♻️ refactor(components): migrate all table components to use Form API
- Refactor LogsTable, MjLogsTable, TokensTable, UsersTable, and ChannelsTable to use Semi-UI Form components - Replace individual input state management with centralized Form API - Add form validation and consistent form handling across all tables - Implement auto-search functionality with proper state update timing - Add reset functionality to clear all search filters - Improve responsive layout design for better mobile experience - Remove duplicate form initial values and consolidate form logic - Remove column visibility feature from ChannelsTable to simplify UI - Standardize search form structure and styling across all table components - Fix state update timing issues in search functionality - Add proper form submission handling with loading states BREAKING CHANGE: Form state management has been completely rewritten. All table components now use Form API instead of individual useState hooks. Column visibility settings for ChannelsTable have been removed.
This commit is contained in:
@@ -25,9 +25,8 @@ import {
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Checkbox,
|
||||
Card,
|
||||
Select
|
||||
Form
|
||||
} from '@douyinfe/semi-ui';
|
||||
import EditChannel from '../../pages/Channel/EditChannel.js';
|
||||
import {
|
||||
@@ -149,108 +148,17 @@ const ChannelsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Define column keys for selection
|
||||
const COLUMN_KEYS = {
|
||||
ID: 'id',
|
||||
NAME: 'name',
|
||||
GROUP: 'group',
|
||||
TYPE: 'type',
|
||||
STATUS: 'status',
|
||||
RESPONSE_TIME: 'response_time',
|
||||
BALANCE: 'balance',
|
||||
PRIORITY: 'priority',
|
||||
WEIGHT: 'weight',
|
||||
OPERATE: 'operate',
|
||||
};
|
||||
|
||||
// State for column visibility
|
||||
const [visibleColumns, setVisibleColumns] = useState({});
|
||||
const [showColumnSelector, setShowColumnSelector] = useState(false);
|
||||
|
||||
// Load saved column preferences from localStorage
|
||||
useEffect(() => {
|
||||
const savedColumns = localStorage.getItem('channels-table-columns');
|
||||
if (savedColumns) {
|
||||
try {
|
||||
const parsed = JSON.parse(savedColumns);
|
||||
// Make sure all columns are accounted for
|
||||
const defaults = getDefaultColumnVisibility();
|
||||
const merged = { ...defaults, ...parsed };
|
||||
setVisibleColumns(merged);
|
||||
} catch (e) {
|
||||
console.error('Failed to parse saved column preferences', e);
|
||||
initDefaultColumns();
|
||||
}
|
||||
} else {
|
||||
initDefaultColumns();
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Update table when column visibility changes
|
||||
useEffect(() => {
|
||||
if (Object.keys(visibleColumns).length > 0) {
|
||||
// Save to localStorage
|
||||
localStorage.setItem(
|
||||
'channels-table-columns',
|
||||
JSON.stringify(visibleColumns),
|
||||
);
|
||||
}
|
||||
}, [visibleColumns]);
|
||||
|
||||
// Get default column visibility
|
||||
const getDefaultColumnVisibility = () => {
|
||||
return {
|
||||
[COLUMN_KEYS.ID]: true,
|
||||
[COLUMN_KEYS.NAME]: true,
|
||||
[COLUMN_KEYS.GROUP]: true,
|
||||
[COLUMN_KEYS.TYPE]: true,
|
||||
[COLUMN_KEYS.STATUS]: true,
|
||||
[COLUMN_KEYS.RESPONSE_TIME]: true,
|
||||
[COLUMN_KEYS.BALANCE]: true,
|
||||
[COLUMN_KEYS.PRIORITY]: true,
|
||||
[COLUMN_KEYS.WEIGHT]: true,
|
||||
[COLUMN_KEYS.OPERATE]: true,
|
||||
};
|
||||
};
|
||||
|
||||
// Initialize default column visibility
|
||||
const initDefaultColumns = () => {
|
||||
const defaults = getDefaultColumnVisibility();
|
||||
setVisibleColumns(defaults);
|
||||
};
|
||||
|
||||
// Handle column visibility change
|
||||
const handleColumnVisibilityChange = (columnKey, checked) => {
|
||||
const updatedColumns = { ...visibleColumns, [columnKey]: checked };
|
||||
setVisibleColumns(updatedColumns);
|
||||
};
|
||||
|
||||
// Handle "Select All" checkbox
|
||||
const handleSelectAll = (checked) => {
|
||||
const allKeys = Object.keys(COLUMN_KEYS).map((key) => COLUMN_KEYS[key]);
|
||||
const updatedColumns = {};
|
||||
|
||||
allKeys.forEach((key) => {
|
||||
updatedColumns[key] = checked;
|
||||
});
|
||||
|
||||
setVisibleColumns(updatedColumns);
|
||||
};
|
||||
|
||||
// Define all columns with keys
|
||||
const allColumns = [
|
||||
// Define all columns
|
||||
const columns = [
|
||||
{
|
||||
key: COLUMN_KEYS.ID,
|
||||
title: t('ID'),
|
||||
dataIndex: 'id',
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.NAME,
|
||||
title: t('名称'),
|
||||
dataIndex: 'name',
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.GROUP,
|
||||
title: t('分组'),
|
||||
dataIndex: 'group',
|
||||
render: (text, record, index) => (
|
||||
@@ -269,7 +177,6 @@ const ChannelsTable = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.TYPE,
|
||||
title: t('类型'),
|
||||
dataIndex: 'type',
|
||||
render: (text, record, index) => {
|
||||
@@ -281,7 +188,6 @@ const ChannelsTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.STATUS,
|
||||
title: t('状态'),
|
||||
dataIndex: 'status',
|
||||
render: (text, record, index) => {
|
||||
@@ -307,7 +213,6 @@ const ChannelsTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.RESPONSE_TIME,
|
||||
title: t('响应时间'),
|
||||
dataIndex: 'response_time',
|
||||
render: (text, record, index) => (
|
||||
@@ -315,7 +220,6 @@ const ChannelsTable = () => {
|
||||
),
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.BALANCE,
|
||||
title: t('已用/剩余'),
|
||||
dataIndex: 'expired_time',
|
||||
render: (text, record, index) => {
|
||||
@@ -354,7 +258,6 @@ const ChannelsTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.PRIORITY,
|
||||
title: t('优先级'),
|
||||
dataIndex: 'priority',
|
||||
render: (text, record, index) => {
|
||||
@@ -406,7 +309,6 @@ const ChannelsTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.WEIGHT,
|
||||
title: t('权重'),
|
||||
dataIndex: 'weight',
|
||||
render: (text, record, index) => {
|
||||
@@ -458,7 +360,6 @@ const ChannelsTable = () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
key: COLUMN_KEYS.OPERATE,
|
||||
title: '',
|
||||
dataIndex: 'operate',
|
||||
fixed: 'right',
|
||||
@@ -631,96 +532,10 @@ const ChannelsTable = () => {
|
||||
},
|
||||
];
|
||||
|
||||
// Filter columns based on visibility settings
|
||||
const getVisibleColumns = () => {
|
||||
return allColumns.filter((column) => visibleColumns[column.key]);
|
||||
};
|
||||
|
||||
// Column selector modal
|
||||
const renderColumnSelector = () => {
|
||||
return (
|
||||
<Modal
|
||||
title={t('列设置')}
|
||||
visible={showColumnSelector}
|
||||
onCancel={() => setShowColumnSelector(false)}
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
theme="light"
|
||||
onClick={() => initDefaultColumns()}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="light"
|
||||
onClick={() => setShowColumnSelector(false)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('取消')}
|
||||
</Button>
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={() => setShowColumnSelector(false)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('确定')}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
size="middle"
|
||||
centered={true}
|
||||
>
|
||||
<div style={{ marginBottom: 20 }}>
|
||||
<Checkbox
|
||||
checked={Object.values(visibleColumns).every((v) => v === true)}
|
||||
indeterminate={
|
||||
Object.values(visibleColumns).some((v) => v === true) &&
|
||||
!Object.values(visibleColumns).every((v) => v === true)
|
||||
}
|
||||
onChange={(e) => handleSelectAll(e.target.checked)}
|
||||
>
|
||||
{t('全选')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
<div
|
||||
className="flex flex-wrap max-h-96 overflow-y-auto rounded-lg p-4"
|
||||
style={{ border: '1px solid var(--semi-color-border)' }}
|
||||
>
|
||||
{allColumns.map((column) => {
|
||||
// Skip columns without title
|
||||
if (!column.title) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={column.key}
|
||||
className="w-1/2 mb-4 pr-2"
|
||||
>
|
||||
<Checkbox
|
||||
checked={!!visibleColumns[column.key]}
|
||||
onChange={(e) =>
|
||||
handleColumnVisibilityChange(column.key, e.target.checked)
|
||||
}
|
||||
>
|
||||
{column.title}
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
const [channels, setChannels] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [idSort, setIdSort] = useState(false);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searchGroup, setSearchGroup] = useState('');
|
||||
const [searchModel, setSearchModel] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [channelCount, setChannelCount] = useState(pageSize);
|
||||
@@ -745,6 +560,16 @@ const ChannelsTable = () => {
|
||||
const [testQueue, setTestQueue] = useState([]);
|
||||
const [isProcessingQueue, setIsProcessingQueue] = useState(false);
|
||||
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
searchKeyword: '',
|
||||
searchGroup: '',
|
||||
searchModel: '',
|
||||
};
|
||||
|
||||
const removeRecord = (record) => {
|
||||
let newDataSource = [...channels];
|
||||
if (record.id != null) {
|
||||
@@ -896,15 +721,11 @@ const ChannelsTable = () => {
|
||||
};
|
||||
|
||||
const refresh = async () => {
|
||||
const { searchKeyword, searchGroup, searchModel } = getFormValues();
|
||||
if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
||||
await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
|
||||
} else {
|
||||
await searchChannels(
|
||||
searchKeyword,
|
||||
searchGroup,
|
||||
searchModel,
|
||||
enableTagMode,
|
||||
);
|
||||
await searchChannels(enableTagMode);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1010,12 +831,19 @@ const ChannelsTable = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const searchChannels = async (
|
||||
searchKeyword,
|
||||
searchGroup,
|
||||
searchModel,
|
||||
enableTagMode,
|
||||
) => {
|
||||
// 获取表单值的辅助函数,确保所有值都是字符串
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
return {
|
||||
searchKeyword: formValues.searchKeyword || '',
|
||||
searchGroup: formValues.searchGroup || '',
|
||||
searchModel: formValues.searchModel || '',
|
||||
};
|
||||
};
|
||||
|
||||
const searchChannels = async (enableTagMode) => {
|
||||
const { searchKeyword, searchGroup, searchModel } = getFormValues();
|
||||
|
||||
if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
||||
await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
|
||||
// setActivePage(1);
|
||||
@@ -1540,71 +1368,83 @@ const ChannelsTable = () => {
|
||||
>
|
||||
{t('刷新')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2">
|
||||
<div className="relative w-full md:w-64">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索渠道的 ID,名称,密钥和API地址 ...')}
|
||||
value={searchKeyword}
|
||||
loading={searching}
|
||||
onChange={(v) => {
|
||||
setSearchKeyword(v.trim());
|
||||
}}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Input
|
||||
prefix={<IconFilter />}
|
||||
placeholder={t('模型关键字')}
|
||||
value={searchModel}
|
||||
loading={searching}
|
||||
onChange={(v) => {
|
||||
setSearchModel(v.trim());
|
||||
}}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Select
|
||||
placeholder={t('选择分组')}
|
||||
optionList={[
|
||||
{ label: t('选择分组'), value: null },
|
||||
...groupOptions,
|
||||
]}
|
||||
value={searchGroup}
|
||||
onChange={(v) => {
|
||||
setSearchGroup(v);
|
||||
searchChannels(searchKeyword, v, searchModel, enableTagMode);
|
||||
}}
|
||||
className="!rounded-full w-full"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
searchChannels(searchKeyword, searchGroup, searchModel, enableTagMode);
|
||||
}}
|
||||
loading={searching}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={() => searchChannels(enableTagMode)}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
className="flex flex-col md:flex-row items-center gap-4 w-full"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<div className="relative w-full md:w-64">
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索渠道的 ID,名称,密钥和API地址 ...')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Form.Input
|
||||
field="searchModel"
|
||||
prefix={<IconFilter />}
|
||||
placeholder={t('模型关键字')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Form.Select
|
||||
field="searchGroup"
|
||||
placeholder={t('选择分组')}
|
||||
optionList={[
|
||||
{ label: t('选择分组'), value: null },
|
||||
...groupOptions,
|
||||
]}
|
||||
className="!rounded-full w-full"
|
||||
showClear
|
||||
pure
|
||||
onChange={() => {
|
||||
// 延迟执行搜索,让表单值先更新
|
||||
setTimeout(() => {
|
||||
searchChannels(enableTagMode);
|
||||
}, 0);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={searching}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
refresh();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1612,7 +1452,6 @@ const ChannelsTable = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderColumnSelector()}
|
||||
<EditTagModal
|
||||
visible={showEditTag}
|
||||
tag={editingTag}
|
||||
@@ -1633,7 +1472,7 @@ const ChannelsTable = () => {
|
||||
bordered={false}
|
||||
>
|
||||
<Table
|
||||
columns={getVisibleColumns()}
|
||||
columns={columns}
|
||||
dataSource={pageData}
|
||||
scroll={{ x: 'max-content' }}
|
||||
pagination={{
|
||||
|
||||
@@ -1204,18 +1204,6 @@ const LogsTable = () => {
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
onValueChange={(values, changedValue) => {
|
||||
// 实时监听日志类型变化
|
||||
if (changedValue.logType !== undefined) {
|
||||
setLogType(parseInt(changedValue.logType));
|
||||
// 日志类型变化时自动搜索,不传入logType参数让其从表单获取最新值
|
||||
setTimeout(() => {
|
||||
setActivePage(1);
|
||||
handleEyeClick();
|
||||
loadLogs(1, pageSize); // 不传入logType参数
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
>
|
||||
@@ -1228,6 +1216,7 @@ const LogsTable = () => {
|
||||
className='w-full'
|
||||
type='dateTimeRange'
|
||||
placeholder={[t('开始时间'), t('结束时间')]}
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
@@ -1239,6 +1228,12 @@ const LogsTable = () => {
|
||||
className='!rounded-full'
|
||||
showClear
|
||||
pure
|
||||
onChange={() => {
|
||||
// 延迟执行搜索,让表单值先更新
|
||||
setTimeout(() => {
|
||||
refresh();
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
<Form.Select.Option value='0'>{t('全部')}</Form.Select.Option>
|
||||
<Form.Select.Option value='1'>{t('充值')}</Form.Select.Option>
|
||||
|
||||
@@ -13,10 +13,9 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Form,
|
||||
ImagePreview,
|
||||
Input,
|
||||
Layout,
|
||||
Modal,
|
||||
Progress,
|
||||
@@ -571,7 +570,6 @@ const LogsTable = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
|
||||
const [logType, setLogType] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
||||
const [showBanner, setShowBanner] = useState(false);
|
||||
@@ -579,22 +577,44 @@ const LogsTable = () => {
|
||||
// 定义模态框图片URL的状态和更新函数
|
||||
const [modalImageUrl, setModalImageUrl] = useState('');
|
||||
let now = new Date();
|
||||
// 初始化start_timestamp为前一天
|
||||
const [inputs, setInputs] = useState({
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
channel_id: '',
|
||||
mj_id: '',
|
||||
start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000),
|
||||
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600),
|
||||
});
|
||||
const { channel_id, mj_id, start_timestamp, end_timestamp } = inputs;
|
||||
dateRange: [
|
||||
timestamp2string(now.getTime() / 1000 - 2592000),
|
||||
timestamp2string(now.getTime() / 1000 + 3600)
|
||||
],
|
||||
};
|
||||
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
const [stat, setStat] = useState({
|
||||
quota: 0,
|
||||
token: 0,
|
||||
});
|
||||
|
||||
const handleInputChange = (value, name) => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
// 获取表单值的辅助函数
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
|
||||
// 处理时间范围
|
||||
let start_timestamp = timestamp2string(now.getTime() / 1000 - 2592000);
|
||||
let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
|
||||
|
||||
if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) {
|
||||
start_timestamp = formValues.dateRange[0];
|
||||
end_timestamp = formValues.dateRange[1];
|
||||
}
|
||||
|
||||
return {
|
||||
channel_id: formValues.channel_id || '',
|
||||
mj_id: formValues.mj_id || '',
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
};
|
||||
};
|
||||
|
||||
const setLogsFormat = (logs) => {
|
||||
@@ -612,6 +632,7 @@ const LogsTable = () => {
|
||||
setLoading(true);
|
||||
|
||||
let url = '';
|
||||
const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues();
|
||||
let localStartTimestamp = Date.parse(start_timestamp);
|
||||
let localEndTimestamp = Date.parse(end_timestamp);
|
||||
if (isAdminUser) {
|
||||
@@ -674,7 +695,7 @@ const LogsTable = () => {
|
||||
const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE;
|
||||
setPageSize(localPageSize);
|
||||
loadLogs(0, localPageSize).then();
|
||||
}, [logType]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled');
|
||||
@@ -789,70 +810,93 @@ const LogsTable = () => {
|
||||
<Divider margin="12px" />
|
||||
|
||||
{/* 搜索表单区域 */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* 时间选择器 */}
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<DatePicker
|
||||
className="w-full"
|
||||
value={[start_timestamp, end_timestamp]}
|
||||
type='dateTimeRange'
|
||||
onChange={(value) => {
|
||||
if (Array.isArray(value) && value.length === 2) {
|
||||
handleInputChange(value[0], 'start_timestamp');
|
||||
handleInputChange(value[1], 'end_timestamp');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={refresh}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* 时间选择器 */}
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<Form.DatePicker
|
||||
field='dateRange'
|
||||
className="w-full"
|
||||
type='dateTimeRange'
|
||||
placeholder={[t('开始时间'), t('结束时间')]}
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 任务 ID */}
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('任务 ID')}
|
||||
value={mj_id}
|
||||
onChange={(value) => handleInputChange(value, 'mj_id')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
|
||||
{/* 渠道 ID - 仅管理员可见 */}
|
||||
{isAdminUser && (
|
||||
<Input
|
||||
{/* 任务 ID */}
|
||||
<Form.Input
|
||||
field='mj_id'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('渠道 ID')}
|
||||
value={channel_id}
|
||||
onChange={(value) => handleInputChange(value, 'channel_id')}
|
||||
placeholder={t('任务 ID')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div></div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={refresh}
|
||||
loading={loading}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
{/* 渠道 ID - 仅管理员可见 */}
|
||||
{isAdminUser && (
|
||||
<Form.Input
|
||||
field='channel_id'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('渠道 ID')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div></div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
loading={loading}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
refresh();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
shadows='always'
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
Card,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Input,
|
||||
Form,
|
||||
Modal,
|
||||
Popover,
|
||||
Space,
|
||||
@@ -223,7 +223,6 @@ const RedemptionsTable = () => {
|
||||
const [redemptions, setRedemptions] = useState([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE);
|
||||
const [selectedKeys, setSelectedKeys] = useState([]);
|
||||
@@ -233,6 +232,22 @@ const RedemptionsTable = () => {
|
||||
});
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
searchKeyword: '',
|
||||
};
|
||||
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
// 获取表单值的辅助函数
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
return {
|
||||
searchKeyword: formValues.searchKeyword || '',
|
||||
};
|
||||
};
|
||||
|
||||
const closeEdit = () => {
|
||||
setShowEdit(false);
|
||||
setTimeout(() => {
|
||||
@@ -340,8 +355,14 @@ const RedemptionsTable = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const searchRedemptions = async (keyword, page, pageSize) => {
|
||||
if (searchKeyword === '') {
|
||||
const searchRedemptions = async (keyword = null, page, pageSize) => {
|
||||
// 如果没有传递keyword参数,从表单获取值
|
||||
if (keyword === null) {
|
||||
const formValues = getFormValues();
|
||||
keyword = formValues.searchKeyword;
|
||||
}
|
||||
|
||||
if (keyword === '') {
|
||||
await loadRedemptions(page, pageSize);
|
||||
return;
|
||||
}
|
||||
@@ -361,10 +382,6 @@ const RedemptionsTable = () => {
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleKeywordChange = async (value) => {
|
||||
setSearchKeyword(value.trim());
|
||||
};
|
||||
|
||||
const sortRedemption = (key) => {
|
||||
if (redemptions.length === 0) return;
|
||||
setLoading(true);
|
||||
@@ -381,6 +398,7 @@ const RedemptionsTable = () => {
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
const { searchKeyword } = getFormValues();
|
||||
if (searchKeyword === '') {
|
||||
loadRedemptions(page, pageSize).then();
|
||||
} else {
|
||||
@@ -457,28 +475,59 @@ const RedemptionsTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2">
|
||||
<div className="relative w-full md:w-64">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('关键字(id或者名称)')}
|
||||
value={searchKeyword}
|
||||
onChange={handleKeywordChange}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={() => {
|
||||
setActivePage(1);
|
||||
searchRedemptions(null, 1, pageSize);
|
||||
}}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
className="w-full md:w-auto order-1 md:order-2"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
||||
<div className="relative w-full md:w-64">
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('关键字(id或者名称)')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 w-full md:w-auto">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={searching}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="light"
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
setActivePage(1);
|
||||
loadRedemptions(1, pageSize);
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
searchRedemptions(searchKeyword, 1, pageSize).then();
|
||||
}}
|
||||
loading={searching}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -517,6 +566,7 @@ const RedemptionsTable = () => {
|
||||
onPageSizeChange: (size) => {
|
||||
setPageSize(size);
|
||||
setActivePage(1);
|
||||
const { searchKeyword } = getFormValues();
|
||||
if (searchKeyword === '') {
|
||||
loadRedemptions(1, size).then();
|
||||
} else {
|
||||
|
||||
@@ -13,9 +13,8 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
DatePicker,
|
||||
Divider,
|
||||
Input,
|
||||
Form,
|
||||
Layout,
|
||||
Modal,
|
||||
Progress,
|
||||
@@ -437,21 +436,43 @@ const LogsTable = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
|
||||
const [logType] = useState(0);
|
||||
|
||||
let now = new Date();
|
||||
// 初始化start_timestamp为前一天
|
||||
let zeroNow = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const [inputs, setInputs] = useState({
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
channel_id: '',
|
||||
task_id: '',
|
||||
start_timestamp: timestamp2string(zeroNow.getTime() / 1000),
|
||||
end_timestamp: '',
|
||||
});
|
||||
const { channel_id, task_id, start_timestamp, end_timestamp } = inputs;
|
||||
dateRange: [
|
||||
timestamp2string(zeroNow.getTime() / 1000),
|
||||
timestamp2string(now.getTime() / 1000 + 3600)
|
||||
],
|
||||
};
|
||||
|
||||
const handleInputChange = (value, name) => {
|
||||
setInputs((inputs) => ({ ...inputs, [name]: value }));
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
// 获取表单值的辅助函数
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
|
||||
// 处理时间范围
|
||||
let start_timestamp = timestamp2string(zeroNow.getTime() / 1000);
|
||||
let end_timestamp = timestamp2string(now.getTime() / 1000 + 3600);
|
||||
|
||||
if (formValues.dateRange && Array.isArray(formValues.dateRange) && formValues.dateRange.length === 2) {
|
||||
start_timestamp = formValues.dateRange[0];
|
||||
end_timestamp = formValues.dateRange[1];
|
||||
}
|
||||
|
||||
return {
|
||||
channel_id: formValues.channel_id || '',
|
||||
task_id: formValues.task_id || '',
|
||||
start_timestamp,
|
||||
end_timestamp,
|
||||
};
|
||||
};
|
||||
|
||||
const setLogsFormat = (logs) => {
|
||||
@@ -469,6 +490,7 @@ const LogsTable = () => {
|
||||
setLoading(true);
|
||||
|
||||
let url = '';
|
||||
const { channel_id, task_id, start_timestamp, end_timestamp } = getFormValues();
|
||||
let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000);
|
||||
let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000);
|
||||
if (isAdminUser) {
|
||||
@@ -528,7 +550,7 @@ const LogsTable = () => {
|
||||
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
|
||||
setPageSize(localPageSize);
|
||||
loadLogs(0, localPageSize).then();
|
||||
}, [logType]);
|
||||
}, []);
|
||||
|
||||
// 列选择器模态框
|
||||
const renderColumnSelector = () => {
|
||||
@@ -628,70 +650,93 @@ const LogsTable = () => {
|
||||
<Divider margin="12px" />
|
||||
|
||||
{/* 搜索表单区域 */}
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* 时间选择器 */}
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<DatePicker
|
||||
className="w-full"
|
||||
value={[start_timestamp, end_timestamp]}
|
||||
type='dateTimeRange'
|
||||
onChange={(value) => {
|
||||
if (Array.isArray(value) && value.length === 2) {
|
||||
handleInputChange(value[0], 'start_timestamp');
|
||||
handleInputChange(value[1], 'end_timestamp');
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={refresh}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="vertical"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* 时间选择器 */}
|
||||
<div className="col-span-1 lg:col-span-2">
|
||||
<Form.DatePicker
|
||||
field='dateRange'
|
||||
className="w-full"
|
||||
type='dateTimeRange'
|
||||
placeholder={[t('开始时间'), t('结束时间')]}
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 任务 ID */}
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('任务 ID')}
|
||||
value={task_id}
|
||||
onChange={(value) => handleInputChange(value, 'task_id')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
|
||||
{/* 渠道 ID - 仅管理员可见 */}
|
||||
{isAdminUser && (
|
||||
<Input
|
||||
{/* 任务 ID */}
|
||||
<Form.Input
|
||||
field='task_id'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('渠道 ID')}
|
||||
value={channel_id}
|
||||
onChange={(value) => handleInputChange(value, 'channel_id')}
|
||||
placeholder={t('任务 ID')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div></div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type='primary'
|
||||
onClick={refresh}
|
||||
loading={loading}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
{/* 渠道 ID - 仅管理员可见 */}
|
||||
{isAdminUser && (
|
||||
<Form.Input
|
||||
field='channel_id'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('渠道 ID')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 操作按钮区域 */}
|
||||
<div className="flex justify-between items-center pt-2">
|
||||
<div></div>
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
type='primary'
|
||||
htmlType='submit'
|
||||
loading={loading}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
refresh();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
<Button
|
||||
theme='light'
|
||||
type='tertiary'
|
||||
icon={<IconSetting />}
|
||||
onClick={() => setShowColumnSelector(true)}
|
||||
className="!rounded-full"
|
||||
>
|
||||
{t('列设置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
}
|
||||
shadows='always'
|
||||
|
||||
@@ -14,12 +14,12 @@ import {
|
||||
Button,
|
||||
Card,
|
||||
Dropdown,
|
||||
Form,
|
||||
Modal,
|
||||
Space,
|
||||
SplitButtonGroup,
|
||||
Table,
|
||||
Tag,
|
||||
Input,
|
||||
} from '@douyinfe/semi-ui';
|
||||
|
||||
import {
|
||||
@@ -335,14 +335,29 @@ const TokensTable = () => {
|
||||
const [tokenCount, setTokenCount] = useState(pageSize);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searchToken, setSearchToken] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [chats, setChats] = useState([]);
|
||||
const [editingToken, setEditingToken] = useState({
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
searchKeyword: '',
|
||||
searchToken: '',
|
||||
};
|
||||
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
// 获取表单值的辅助函数
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
return {
|
||||
searchKeyword: formValues.searchKeyword || '',
|
||||
searchToken: formValues.searchToken || '',
|
||||
};
|
||||
};
|
||||
|
||||
const closeEdit = () => {
|
||||
setShowEdit(false);
|
||||
setTimeout(() => {
|
||||
@@ -416,8 +431,6 @@ const TokensTable = () => {
|
||||
window.open(url, '_blank');
|
||||
};
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
loadTokens(0)
|
||||
.then()
|
||||
@@ -472,6 +485,7 @@ const TokensTable = () => {
|
||||
};
|
||||
|
||||
const searchTokens = async () => {
|
||||
const { searchKeyword, searchToken } = getFormValues();
|
||||
if (searchKeyword === '' && searchToken === '') {
|
||||
await loadTokens(0);
|
||||
setActivePage(1);
|
||||
@@ -491,14 +505,6 @@ const TokensTable = () => {
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleKeywordChange = async (value) => {
|
||||
setSearchKeyword(value.trim());
|
||||
};
|
||||
|
||||
const handleSearchTokenChange = async (value) => {
|
||||
setSearchToken(value.trim());
|
||||
};
|
||||
|
||||
const sortToken = (key) => {
|
||||
if (tokens.length === 0) return;
|
||||
setLoading(true);
|
||||
@@ -580,36 +586,65 @@ const TokensTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2">
|
||||
<div className="relative w-full md:w-56">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索关键字')}
|
||||
value={searchKeyword}
|
||||
onChange={handleKeywordChange}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={searchTokens}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
className="w-full md:w-auto order-1 md:order-2"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
||||
<div className="relative w-full md:w-56">
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索关键字')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="relative w-full md:w-56">
|
||||
<Form.Input
|
||||
field="searchToken"
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('密钥')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 w-full md:w-auto">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={searching}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="light"
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
searchTokens();
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full md:w-56">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('密钥')}
|
||||
value={searchToken}
|
||||
onChange={handleSearchTokenChange}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={searchTokens}
|
||||
loading={searching}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,9 +5,8 @@ import {
|
||||
Card,
|
||||
Divider,
|
||||
Dropdown,
|
||||
Input,
|
||||
Form,
|
||||
Modal,
|
||||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tag,
|
||||
@@ -285,9 +284,7 @@ const UsersTable = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [activePage, setActivePage] = useState(1);
|
||||
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
const [searching, setSearching] = useState(false);
|
||||
const [searchGroup, setSearchGroup] = useState('');
|
||||
const [groupOptions, setGroupOptions] = useState([]);
|
||||
const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
|
||||
const [showAddUser, setShowAddUser] = useState(false);
|
||||
@@ -296,6 +293,24 @@ const UsersTable = () => {
|
||||
id: undefined,
|
||||
});
|
||||
|
||||
// Form 初始值
|
||||
const formInitValues = {
|
||||
searchKeyword: '',
|
||||
searchGroup: '',
|
||||
};
|
||||
|
||||
// Form API 引用
|
||||
const [formApi, setFormApi] = useState(null);
|
||||
|
||||
// 获取表单值的辅助函数
|
||||
const getFormValues = () => {
|
||||
const formValues = formApi ? formApi.getValues() : {};
|
||||
return {
|
||||
searchKeyword: formValues.searchKeyword || '',
|
||||
searchGroup: formValues.searchGroup || '',
|
||||
};
|
||||
};
|
||||
|
||||
const removeRecord = (key) => {
|
||||
let newDataSource = [...users];
|
||||
if (key != null) {
|
||||
@@ -363,9 +378,16 @@ const UsersTable = () => {
|
||||
const searchUsers = async (
|
||||
startIdx,
|
||||
pageSize,
|
||||
searchKeyword,
|
||||
searchGroup,
|
||||
searchKeyword = null,
|
||||
searchGroup = null,
|
||||
) => {
|
||||
// 如果没有传递参数,从表单获取值
|
||||
if (searchKeyword === null || searchGroup === null) {
|
||||
const formValues = getFormValues();
|
||||
searchKeyword = formValues.searchKeyword;
|
||||
searchGroup = formValues.searchGroup;
|
||||
}
|
||||
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
// if keyword is blank, load files instead.
|
||||
await loadUsers(startIdx, pageSize);
|
||||
@@ -387,12 +409,9 @@ const UsersTable = () => {
|
||||
setSearching(false);
|
||||
};
|
||||
|
||||
const handleKeywordChange = async (value) => {
|
||||
setSearchKeyword(value.trim());
|
||||
};
|
||||
|
||||
const handlePageChange = (page) => {
|
||||
setActivePage(page);
|
||||
const { searchKeyword, searchGroup } = getFormValues();
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
loadUsers(page, pageSize).then();
|
||||
} else {
|
||||
@@ -413,10 +432,11 @@ const UsersTable = () => {
|
||||
|
||||
const refresh = async () => {
|
||||
setActivePage(1);
|
||||
if (searchKeyword === '') {
|
||||
await loadUsers(activePage, pageSize);
|
||||
const { searchKeyword, searchGroup } = getFormValues();
|
||||
if (searchKeyword === '' && searchGroup === '') {
|
||||
await loadUsers(1, pageSize);
|
||||
} else {
|
||||
await searchUsers(activePage, pageSize, searchKeyword, searchGroup);
|
||||
await searchUsers(1, pageSize, searchKeyword, searchGroup);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -488,41 +508,76 @@ const UsersTable = () => {
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2">
|
||||
<div className="relative w-full md:w-64">
|
||||
<Input
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')}
|
||||
value={searchKeyword}
|
||||
onChange={handleKeywordChange}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
/>
|
||||
<Form
|
||||
initValues={formInitValues}
|
||||
getFormApi={(api) => setFormApi(api)}
|
||||
onSubmit={() => {
|
||||
setActivePage(1);
|
||||
searchUsers(1, pageSize);
|
||||
}}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
stopValidateWithError={false}
|
||||
className="w-full md:w-auto order-1 md:order-2"
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
|
||||
<div className="relative w-full md:w-64">
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')}
|
||||
className="!rounded-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Form.Select
|
||||
field="searchGroup"
|
||||
placeholder={t('选择分组')}
|
||||
optionList={groupOptions}
|
||||
onChange={(value) => {
|
||||
// 分组变化时自动搜索
|
||||
setTimeout(() => {
|
||||
setActivePage(1);
|
||||
searchUsers(1, pageSize);
|
||||
}, 100);
|
||||
}}
|
||||
className="!rounded-full w-full"
|
||||
showClear
|
||||
pure
|
||||
/>
|
||||
</div>
|
||||
<div className="flex gap-2 w-full md:w-auto">
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={searching}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
<Button
|
||||
theme="light"
|
||||
onClick={() => {
|
||||
if (formApi) {
|
||||
formApi.reset();
|
||||
// 重置后立即查询,使用setTimeout确保表单重置完成
|
||||
setTimeout(() => {
|
||||
setActivePage(1);
|
||||
loadUsers(1, pageSize);
|
||||
}, 100);
|
||||
}
|
||||
}}
|
||||
className="!rounded-full flex-1 md:flex-initial md:w-auto"
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<Select
|
||||
placeholder={t('选择分组')}
|
||||
optionList={groupOptions}
|
||||
value={searchGroup}
|
||||
onChange={(value) => {
|
||||
setSearchGroup(value);
|
||||
searchUsers(activePage, pageSize, searchKeyword, value);
|
||||
}}
|
||||
className="!rounded-full w-full"
|
||||
showClear
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
searchUsers(activePage, pageSize, searchKeyword, searchGroup);
|
||||
}}
|
||||
loading={searching}
|
||||
className="!rounded-full w-full md:w-auto"
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user