✨ feat(ui): Implement unified compact/adaptive table mode + icon refinement
Summary
• Added per-table “Compact / Adaptive” view toggle to all major table components (Tokens, Channels, Logs, MjLogs, TaskLogs, Redemptions, Users).
• Persist user preference in a single localStorage entry (`table_compact_modes`) instead of scattered keys.
Details
1. utils.js
• Re-implemented `getTableCompactMode` / `setTableCompactMode` to read & write a shared JSON object.
• Imported storage-key constant from `constants`.
2. hooks/useTableCompactMode.js
• Hook now consumes the unified helpers and listens to `storage` events via the shared key constant.
3. constants
• Added `TABLE_COMPACT_MODES_KEY` to `common.constant.js` and re-exported via `constants/index.js`.
4. Table components
• Integrated `useTableCompactMode('<tableName>')`.
• Dynamically remove `fixed: 'right'` column and horizontal `scroll` when in compact mode.
• UI: toggle button placed at card title’s right; responsive layout on small screens.
5. UI polish
• Replaced all lucide-react `List`/`ListIcon` usages with Semi UI `IconDescend` for consistency.
• Restored correct icons where `Hash` was intended (TaskLogsTable).
Benefits
• Consistent UX for switching list density across the app.
• Cleaner localStorage footprint with easier future maintenance.
This commit is contained in:
@@ -17,7 +17,7 @@ import {
|
||||
AlertCircle,
|
||||
HelpCircle,
|
||||
Coins,
|
||||
Tags
|
||||
Tags,
|
||||
} from 'lucide-react';
|
||||
|
||||
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../../constants/index.js';
|
||||
@@ -52,6 +52,7 @@ import {
|
||||
IconPlus,
|
||||
IconRefresh,
|
||||
IconSetting,
|
||||
IconDescend,
|
||||
IconSearch,
|
||||
IconEdit,
|
||||
IconDelete,
|
||||
@@ -64,6 +65,7 @@ import {
|
||||
import { loadChannelModels } from '../../helpers/index.js';
|
||||
import EditTagModal from '../../pages/Channel/EditTagModal.js';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const ChannelsTable = () => {
|
||||
const { t } = useTranslation();
|
||||
@@ -683,6 +685,7 @@ const ChannelsTable = () => {
|
||||
const [typeCounts, setTypeCounts] = useState({});
|
||||
const requestCounter = useRef(0);
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('channels');
|
||||
const formInitValues = {
|
||||
searchKeyword: '',
|
||||
searchGroup: '',
|
||||
@@ -1576,6 +1579,16 @@ const ChannelsTable = () => {
|
||||
{t('批量操作')}
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-start md:items-center gap-4 w-full md:w-auto order-1 md:order-2">
|
||||
@@ -1766,9 +1779,9 @@ const ChannelsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={getVisibleColumns()}
|
||||
columns={compactMode ? getVisibleColumns().map(({ fixed, ...rest }) => rest) : getVisibleColumns()}
|
||||
dataSource={pageData}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
pagination={{
|
||||
currentPage: activePage,
|
||||
pageSize: pageSize,
|
||||
|
||||
@@ -47,8 +47,9 @@ import {
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import Paragraph from '@douyinfe/semi-ui/lib/es/typography/paragraph';
|
||||
import { IconSetting, IconSearch, IconHelpCircle } from '@douyinfe/semi-icons';
|
||||
import { IconSetting, IconSearch, IconHelpCircle, IconDescend } from '@douyinfe/semi-icons';
|
||||
import { Route } from 'lucide-react';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -192,7 +193,7 @@ const LogsTable = () => {
|
||||
if (!modelMapped) {
|
||||
return renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
},
|
||||
});
|
||||
} else {
|
||||
@@ -209,7 +210,7 @@ const LogsTable = () => {
|
||||
</Text>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
},
|
||||
})}
|
||||
</div>
|
||||
@@ -220,7 +221,7 @@ const LogsTable = () => {
|
||||
{renderModelTag(other.upstream_model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, other.upstream_model_name).then(
|
||||
(r) => {},
|
||||
(r) => { },
|
||||
);
|
||||
},
|
||||
})}
|
||||
@@ -231,7 +232,7 @@ const LogsTable = () => {
|
||||
>
|
||||
{renderModelTag(record.model_name, {
|
||||
onClick: (event) => {
|
||||
copyText(event, record.model_name).then((r) => {});
|
||||
copyText(event, record.model_name).then((r) => { });
|
||||
},
|
||||
suffixIcon: (
|
||||
<Route
|
||||
@@ -636,23 +637,23 @@ const LogsTable = () => {
|
||||
}
|
||||
let content = other?.claude
|
||||
? renderClaudeModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_tokens || 0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
: renderModelPriceSimple(
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
);
|
||||
other.model_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_tokens || 0,
|
||||
other.cache_ratio || 1.0,
|
||||
);
|
||||
return (
|
||||
<Paragraph
|
||||
ellipsis={{
|
||||
@@ -985,27 +986,27 @@ const LogsTable = () => {
|
||||
key: t('日志详情'),
|
||||
value: other?.claude
|
||||
? renderClaudeLogContent(
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
other.cache_ratio || 1.0,
|
||||
other.cache_creation_ratio || 1.0,
|
||||
)
|
||||
: renderLogContent(
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
false,
|
||||
1.0,
|
||||
other.web_search || false,
|
||||
other.web_search_call_count || 0,
|
||||
other.file_search || false,
|
||||
other.file_search_call_count || 0,
|
||||
),
|
||||
other?.model_ratio,
|
||||
other.completion_ratio,
|
||||
other.model_price,
|
||||
other.group_ratio,
|
||||
other?.user_group_ratio,
|
||||
false,
|
||||
1.0,
|
||||
other.web_search || false,
|
||||
other.web_search_call_count || 0,
|
||||
other.file_search || false,
|
||||
other.file_search_call_count || 0,
|
||||
),
|
||||
});
|
||||
}
|
||||
if (logs[i].type === 2) {
|
||||
@@ -1145,7 +1146,7 @@ const LogsTable = () => {
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
loadLogs(page, pageSize).then((r) => {}); // 不传入logType,让其从表单获取最新值
|
||||
loadLogs(page, pageSize).then((r) => { }); // 不传入logType,让其从表单获取最新值
|
||||
};
|
||||
|
||||
const handlePageSizeChange = async (size) => {
|
||||
@@ -1203,6 +1204,8 @@ const LogsTable = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('logs');
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderColumnSelector()}
|
||||
@@ -1211,45 +1214,57 @@ const LogsTable = () => {
|
||||
title={
|
||||
<div className='flex flex-col w-full'>
|
||||
<Spin spinning={loadingStat}>
|
||||
<Space>
|
||||
<Tag
|
||||
color='blue'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
}}
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<Space>
|
||||
<Tag
|
||||
color='blue'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
}}
|
||||
>
|
||||
{t('消耗额度')}: {renderQuota(stat.quota)}
|
||||
</Tag>
|
||||
<Tag
|
||||
color='pink'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
}}
|
||||
>
|
||||
RPM: {stat.rpm}
|
||||
</Tag>
|
||||
<Tag
|
||||
color='white'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
border: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
TPM: {stat.tpm}
|
||||
</Tag>
|
||||
</Space>
|
||||
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{t('消耗额度')}: {renderQuota(stat.quota)}
|
||||
</Tag>
|
||||
<Tag
|
||||
color='pink'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
}}
|
||||
>
|
||||
RPM: {stat.rpm}
|
||||
</Tag>
|
||||
<Tag
|
||||
color='white'
|
||||
size='large'
|
||||
style={{
|
||||
padding: 15,
|
||||
border: 'none',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: '9999px',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
TPM: {stat.tpm}
|
||||
</Tag>
|
||||
</Space>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
</Spin>
|
||||
|
||||
<Divider margin='12px' />
|
||||
@@ -1382,7 +1397,6 @@ const LogsTable = () => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
setLogType(0);
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
refresh();
|
||||
}, 100);
|
||||
@@ -1411,7 +1425,7 @@ const LogsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={getVisibleColumns()}
|
||||
columns={compactMode ? getVisibleColumns().map(({ fixed, ...rest }) => rest) : getVisibleColumns()}
|
||||
{...(hasExpandableRows() && {
|
||||
expandedRowRender: expandRowRender,
|
||||
expandRowByClick: true,
|
||||
@@ -1421,7 +1435,7 @@ const LogsTable = () => {
|
||||
dataSource={logs}
|
||||
rowKey='key'
|
||||
loading={loading}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
className='rounded-xl overflow-hidden'
|
||||
size='middle'
|
||||
empty={
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
XCircle,
|
||||
Loader,
|
||||
AlertCircle,
|
||||
Hash
|
||||
Hash,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
API,
|
||||
@@ -59,8 +59,10 @@ import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import {
|
||||
IconEyeOpened,
|
||||
IconSearch,
|
||||
IconSetting
|
||||
IconSetting,
|
||||
IconDescend
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -107,6 +109,7 @@ const LogsTable = () => {
|
||||
const [visibleColumns, setVisibleColumns] = useState({});
|
||||
const [showColumnSelector, setShowColumnSelector] = useState(false);
|
||||
const isAdminUser = isAdmin();
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('mjLogs');
|
||||
|
||||
// 加载保存的列偏好设置
|
||||
useEffect(() => {
|
||||
@@ -802,7 +805,7 @@ const LogsTable = () => {
|
||||
className="!rounded-2xl mb-4"
|
||||
title={
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-orange-500 mb-2 md:mb-0">
|
||||
<IconEyeOpened className="mr-2" />
|
||||
{loading ? (
|
||||
@@ -821,6 +824,15 @@ const LogsTable = () => {
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider margin="12px" />
|
||||
@@ -919,11 +931,11 @@ const LogsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={getVisibleColumns()}
|
||||
columns={compactMode ? getVisibleColumns().map(({ fixed, ...rest }) => rest) : getVisibleColumns()}
|
||||
dataSource={logs}
|
||||
rowKey='key'
|
||||
loading={loading}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
className="rounded-xl overflow-hidden"
|
||||
size="middle"
|
||||
empty={
|
||||
|
||||
@@ -45,10 +45,12 @@ import {
|
||||
IconDelete,
|
||||
IconStop,
|
||||
IconPlay,
|
||||
IconMore
|
||||
IconMore,
|
||||
IconDescend
|
||||
} from '@douyinfe/semi-icons';
|
||||
import EditRedemption from '../../pages/Redemption/EditRedemption';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -266,6 +268,7 @@ const RedemptionsTable = () => {
|
||||
id: undefined,
|
||||
});
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('redemptions');
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
@@ -465,9 +468,20 @@ const RedemptionsTable = () => {
|
||||
const renderHeader = () => (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center text-orange-500">
|
||||
<Ticket size={16} className="mr-2" />
|
||||
<Text>{t('兑换码可以批量生成和分发,适合用于推广活动或批量充值。')}</Text>
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-orange-500">
|
||||
<Ticket size={16} className="mr-2" />
|
||||
<Text>{t('兑换码可以批量生成和分发,适合用于推广活动或批量充值。')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -610,9 +624,9 @@ const RedemptionsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
columns={compactMode ? columns.map(({ fixed, ...rest }) => rest) : columns}
|
||||
dataSource={pageData}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
pagination={{
|
||||
currentPage: activePage,
|
||||
pageSize: pageSize,
|
||||
|
||||
@@ -47,8 +47,10 @@ import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import {
|
||||
IconEyeOpened,
|
||||
IconSearch,
|
||||
IconSetting
|
||||
IconSetting,
|
||||
IconDescend
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -471,6 +473,8 @@ const LogsTable = () => {
|
||||
const [logs, setLogs] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('taskLogs');
|
||||
|
||||
useEffect(() => {
|
||||
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
|
||||
setPageSize(localPageSize);
|
||||
@@ -650,7 +654,7 @@ const LogsTable = () => {
|
||||
className="!rounded-2xl mb-4"
|
||||
title={
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="flex flex-col md:flex-row justify-between items-center">
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-orange-500 mb-2 md:mb-0">
|
||||
<IconEyeOpened className="mr-2" />
|
||||
{loading ? (
|
||||
@@ -665,6 +669,15 @@ const LogsTable = () => {
|
||||
<Text>{t('任务记录')}</Text>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider margin="12px" />
|
||||
@@ -763,11 +776,11 @@ const LogsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={getVisibleColumns()}
|
||||
columns={compactMode ? getVisibleColumns().map(({ fixed, ...rest }) => rest) : getVisibleColumns()}
|
||||
dataSource={logs}
|
||||
rowKey='key'
|
||||
loading={loading}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
className="rounded-xl overflow-hidden"
|
||||
size="middle"
|
||||
empty={
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useMemo } from 'react';
|
||||
import {
|
||||
API,
|
||||
copy,
|
||||
@@ -52,10 +52,12 @@ import {
|
||||
IconDelete,
|
||||
IconStop,
|
||||
IconPlay,
|
||||
IconMore
|
||||
IconMore,
|
||||
IconDescend
|
||||
} from '@douyinfe/semi-icons';
|
||||
import EditToken from '../../pages/Token/EditToken';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -385,6 +387,7 @@ const TokensTable = () => {
|
||||
const [editingToken, setEditingToken] = useState({
|
||||
id: undefined,
|
||||
});
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('tokens');
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
@@ -610,9 +613,20 @@ const TokensTable = () => {
|
||||
const renderHeader = () => (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center text-blue-500">
|
||||
<Key size={16} className="mr-2" />
|
||||
<Text>{t('令牌用于API访问认证,可以设置额度限制和模型权限。')}</Text>
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-blue-500">
|
||||
<Key size={16} className="mr-2" />
|
||||
<Text>{t('令牌用于API访问认证,可以设置额度限制和模型权限。')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
theme="light"
|
||||
type="secondary"
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -687,7 +701,6 @@ const TokensTable = () => {
|
||||
>
|
||||
{t('复制所选令牌')}
|
||||
</Button>
|
||||
<div className="w-full md:hidden"></div>
|
||||
<Button
|
||||
theme="light"
|
||||
type="danger"
|
||||
@@ -791,9 +804,15 @@ const TokensTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
columns={compactMode ? columns.map(col => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
}) : columns}
|
||||
dataSource={tokens}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
pagination={{
|
||||
currentPage: activePage,
|
||||
pageSize: pageSize,
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
Activity,
|
||||
Users,
|
||||
DollarSign,
|
||||
UserPlus
|
||||
UserPlus,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
Button,
|
||||
@@ -43,17 +43,20 @@ import {
|
||||
IconMore,
|
||||
IconUserAdd,
|
||||
IconArrowUp,
|
||||
IconArrowDown
|
||||
IconArrowDown,
|
||||
IconDescend
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { ITEMS_PER_PAGE } from '../../constants';
|
||||
import AddUser from '../../pages/User/AddUser';
|
||||
import EditUser from '../../pages/User/EditUser';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useTableCompactMode } from '../../hooks/useTableCompactMode';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
const UsersTable = () => {
|
||||
const { t } = useTranslation();
|
||||
const [compactMode, setCompactMode] = useTableCompactMode('users');
|
||||
|
||||
function renderRole(role) {
|
||||
switch (role) {
|
||||
@@ -527,9 +530,20 @@ const UsersTable = () => {
|
||||
const renderHeader = () => (
|
||||
<div className="flex flex-col w-full">
|
||||
<div className="mb-2">
|
||||
<div className="flex items-center text-blue-500">
|
||||
<IconUserAdd className="mr-2" />
|
||||
<Text>{t('用户管理页面,可以查看和管理所有注册用户的信息、权限和状态。')}</Text>
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-blue-500">
|
||||
<IconUserAdd className="mr-2" />
|
||||
<Text>{t('用户管理页面,可以查看和管理所有注册用户的信息、权限和状态。')}</Text>
|
||||
</div>
|
||||
<Button
|
||||
theme='light'
|
||||
type='secondary'
|
||||
icon={<IconDescend />}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
onClick={() => setCompactMode(!compactMode)}
|
||||
>
|
||||
{compactMode ? t('自适应列表') : t('紧凑列表')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -645,9 +659,9 @@ const UsersTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
columns={compactMode ? columns.map(({ fixed, ...rest }) => rest) : columns}
|
||||
dataSource={users}
|
||||
scroll={{ x: 'max-content' }}
|
||||
scroll={compactMode ? undefined : { x: 'max-content' }}
|
||||
pagination={{
|
||||
formatPageText: (page) =>
|
||||
t('第 {{start}} - {{end}} 条,共 {{total}} 条', {
|
||||
|
||||
Reference in New Issue
Block a user