Commit a62a8441 by Apple\Apple

️ 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.
parent d1fb8415
...@@ -1204,18 +1204,6 @@ const LogsTable = () => { ...@@ -1204,18 +1204,6 @@ const LogsTable = () => {
allowEmpty={true} allowEmpty={true}
autoComplete="off" autoComplete="off"
layout="vertical" 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" trigger="change"
stopValidateWithError={false} stopValidateWithError={false}
> >
...@@ -1228,6 +1216,7 @@ const LogsTable = () => { ...@@ -1228,6 +1216,7 @@ const LogsTable = () => {
className='w-full' className='w-full'
type='dateTimeRange' type='dateTimeRange'
placeholder={[t('开始时间'), t('结束时间')]} placeholder={[t('开始时间'), t('结束时间')]}
showClear
pure pure
/> />
</div> </div>
...@@ -1239,6 +1228,12 @@ const LogsTable = () => { ...@@ -1239,6 +1228,12 @@ const LogsTable = () => {
className='!rounded-full' className='!rounded-full'
showClear showClear
pure pure
onChange={() => {
// 延迟执行搜索,让表单值先更新
setTimeout(() => {
refresh();
}, 0);
}}
> >
<Form.Select.Option value='0'>{t('全部')}</Form.Select.Option> <Form.Select.Option value='0'>{t('全部')}</Form.Select.Option>
<Form.Select.Option value='1'>{t('充值')}</Form.Select.Option> <Form.Select.Option value='1'>{t('充值')}</Form.Select.Option>
......
...@@ -13,10 +13,9 @@ import { ...@@ -13,10 +13,9 @@ import {
Button, Button,
Card, Card,
Checkbox, Checkbox,
DatePicker,
Divider, Divider,
Form,
ImagePreview, ImagePreview,
Input,
Layout, Layout,
Modal, Modal,
Progress, Progress,
...@@ -571,7 +570,6 @@ const LogsTable = () => { ...@@ -571,7 +570,6 @@ const LogsTable = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
const [logType, setLogType] = useState(0);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [isModalOpenurl, setIsModalOpenurl] = useState(false); const [isModalOpenurl, setIsModalOpenurl] = useState(false);
const [showBanner, setShowBanner] = useState(false); const [showBanner, setShowBanner] = useState(false);
...@@ -579,22 +577,44 @@ const LogsTable = () => { ...@@ -579,22 +577,44 @@ const LogsTable = () => {
// 定义模态框图片URL的状态和更新函数 // 定义模态框图片URL的状态和更新函数
const [modalImageUrl, setModalImageUrl] = useState(''); const [modalImageUrl, setModalImageUrl] = useState('');
let now = new Date(); let now = new Date();
// 初始化start_timestamp为前一天
const [inputs, setInputs] = useState({ // Form 初始值
const formInitValues = {
channel_id: '', channel_id: '',
mj_id: '', mj_id: '',
start_timestamp: timestamp2string(now.getTime() / 1000 - 2592000), dateRange: [
end_timestamp: timestamp2string(now.getTime() / 1000 + 3600), timestamp2string(now.getTime() / 1000 - 2592000),
}); timestamp2string(now.getTime() / 1000 + 3600)
const { channel_id, mj_id, start_timestamp, end_timestamp } = inputs; ],
};
// Form API 引用
const [formApi, setFormApi] = useState(null);
const [stat, setStat] = useState({ const [stat, setStat] = useState({
quota: 0, quota: 0,
token: 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) => { const setLogsFormat = (logs) => {
...@@ -612,6 +632,7 @@ const LogsTable = () => { ...@@ -612,6 +632,7 @@ const LogsTable = () => {
setLoading(true); setLoading(true);
let url = ''; let url = '';
const { channel_id, mj_id, start_timestamp, end_timestamp } = getFormValues();
let localStartTimestamp = Date.parse(start_timestamp); let localStartTimestamp = Date.parse(start_timestamp);
let localEndTimestamp = Date.parse(end_timestamp); let localEndTimestamp = Date.parse(end_timestamp);
if (isAdminUser) { if (isAdminUser) {
...@@ -674,7 +695,7 @@ const LogsTable = () => { ...@@ -674,7 +695,7 @@ const LogsTable = () => {
const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE; const localPageSize = parseInt(localStorage.getItem('mj-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize); setPageSize(localPageSize);
loadLogs(0, localPageSize).then(); loadLogs(0, localPageSize).then();
}, [logType]); }, []);
useEffect(() => { useEffect(() => {
const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled'); const mjNotifyEnabled = localStorage.getItem('mj_notify_enabled');
...@@ -789,70 +810,93 @@ const LogsTable = () => { ...@@ -789,70 +810,93 @@ const LogsTable = () => {
<Divider margin="12px" /> <Divider margin="12px" />
{/* 搜索表单区域 */} {/* 搜索表单区域 */}
<div className="flex flex-col gap-4"> <Form
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> initValues={formInitValues}
{/* 时间选择器 */} getFormApi={(api) => setFormApi(api)}
<div className="col-span-1 lg:col-span-2"> onSubmit={refresh}
<DatePicker allowEmpty={true}
className="w-full" autoComplete="off"
value={[start_timestamp, end_timestamp]} layout="vertical"
type='dateTimeRange' trigger="change"
onChange={(value) => { stopValidateWithError={false}
if (Array.isArray(value) && value.length === 2) { >
handleInputChange(value[0], 'start_timestamp'); <div className="flex flex-col gap-4">
handleInputChange(value[1], 'end_timestamp'); <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
</div> field='dateRange'
className="w-full"
{/* 任务 ID */} type='dateTimeRange'
<Input placeholder={[t('开始时间'), t('结束时间')]}
prefix={<IconSearch />} showClear
placeholder={t('任务 ID')} pure
value={mj_id} />
onChange={(value) => handleInputChange(value, 'mj_id')} </div>
className="!rounded-full"
showClear {/* 任务 ID */}
/> <Form.Input
field='mj_id'
{/* 渠道 ID - 仅管理员可见 */}
{isAdminUser && (
<Input
prefix={<IconSearch />} prefix={<IconSearch />}
placeholder={t('渠道 ID')} placeholder={t('任务 ID')}
value={channel_id}
onChange={(value) => handleInputChange(value, 'channel_id')}
className="!rounded-full" className="!rounded-full"
showClear showClear
pure
/> />
)}
</div>
{/* 操作按钮区域 */} {/* 渠道 ID - 仅管理员可见 */}
<div className="flex justify-between items-center pt-2"> {isAdminUser && (
<div></div> <Form.Input
<div className="flex gap-2"> field='channel_id'
<Button prefix={<IconSearch />}
type='primary' placeholder={t('渠道 ID')}
onClick={refresh} className="!rounded-full"
loading={loading} showClear
className="!rounded-full" pure
> />
{t('查询')} )}
</Button> </div>
<Button
theme='light' {/* 操作按钮区域 */}
type='tertiary' <div className="flex justify-between items-center pt-2">
icon={<IconSetting />} <div></div>
onClick={() => setShowColumnSelector(true)} <div className="flex gap-2">
className="!rounded-full" <Button
> type='primary'
{t('列设置')} htmlType='submit'
</Button> 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> </div>
</div> </Form>
</div> </div>
} }
shadows='always' shadows='always'
......
...@@ -14,7 +14,7 @@ import { ...@@ -14,7 +14,7 @@ import {
Card, Card,
Divider, Divider,
Dropdown, Dropdown,
Input, Form,
Modal, Modal,
Popover, Popover,
Space, Space,
...@@ -223,7 +223,6 @@ const RedemptionsTable = () => { ...@@ -223,7 +223,6 @@ const RedemptionsTable = () => {
const [redemptions, setRedemptions] = useState([]); const [redemptions, setRedemptions] = useState([]);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [searchKeyword, setSearchKeyword] = useState('');
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE); const [tokenCount, setTokenCount] = useState(ITEMS_PER_PAGE);
const [selectedKeys, setSelectedKeys] = useState([]); const [selectedKeys, setSelectedKeys] = useState([]);
...@@ -233,6 +232,22 @@ const RedemptionsTable = () => { ...@@ -233,6 +232,22 @@ const RedemptionsTable = () => {
}); });
const [showEdit, setShowEdit] = useState(false); 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 = () => { const closeEdit = () => {
setShowEdit(false); setShowEdit(false);
setTimeout(() => { setTimeout(() => {
...@@ -340,8 +355,14 @@ const RedemptionsTable = () => { ...@@ -340,8 +355,14 @@ const RedemptionsTable = () => {
setLoading(false); setLoading(false);
}; };
const searchRedemptions = async (keyword, page, pageSize) => { const searchRedemptions = async (keyword = null, page, pageSize) => {
if (searchKeyword === '') { // 如果没有传递keyword参数,从表单获取值
if (keyword === null) {
const formValues = getFormValues();
keyword = formValues.searchKeyword;
}
if (keyword === '') {
await loadRedemptions(page, pageSize); await loadRedemptions(page, pageSize);
return; return;
} }
...@@ -361,10 +382,6 @@ const RedemptionsTable = () => { ...@@ -361,10 +382,6 @@ const RedemptionsTable = () => {
setSearching(false); setSearching(false);
}; };
const handleKeywordChange = async (value) => {
setSearchKeyword(value.trim());
};
const sortRedemption = (key) => { const sortRedemption = (key) => {
if (redemptions.length === 0) return; if (redemptions.length === 0) return;
setLoading(true); setLoading(true);
...@@ -381,6 +398,7 @@ const RedemptionsTable = () => { ...@@ -381,6 +398,7 @@ const RedemptionsTable = () => {
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); setActivePage(page);
const { searchKeyword } = getFormValues();
if (searchKeyword === '') { if (searchKeyword === '') {
loadRedemptions(page, pageSize).then(); loadRedemptions(page, pageSize).then();
} else { } else {
...@@ -457,28 +475,59 @@ const RedemptionsTable = () => { ...@@ -457,28 +475,59 @@ const RedemptionsTable = () => {
</Button> </Button>
</div> </div>
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2"> <Form
<div className="relative w-full md:w-64"> initValues={formInitValues}
<Input getFormApi={(api) => setFormApi(api)}
prefix={<IconSearch />} onSubmit={() => {
placeholder={t('关键字(id或者名称)')} setActivePage(1);
value={searchKeyword} searchRedemptions(null, 1, pageSize);
onChange={handleKeywordChange} }}
className="!rounded-full" allowEmpty={true}
showClear 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> </div>
<Button </Form>
type="primary"
onClick={() => {
searchRedemptions(searchKeyword, 1, pageSize).then();
}}
loading={searching}
className="!rounded-full w-full md:w-auto"
>
{t('查询')}
</Button>
</div>
</div> </div>
</div> </div>
); );
...@@ -517,6 +566,7 @@ const RedemptionsTable = () => { ...@@ -517,6 +566,7 @@ const RedemptionsTable = () => {
onPageSizeChange: (size) => { onPageSizeChange: (size) => {
setPageSize(size); setPageSize(size);
setActivePage(1); setActivePage(1);
const { searchKeyword } = getFormValues();
if (searchKeyword === '') { if (searchKeyword === '') {
loadRedemptions(1, size).then(); loadRedemptions(1, size).then();
} else { } else {
......
...@@ -13,9 +13,8 @@ import { ...@@ -13,9 +13,8 @@ import {
Button, Button,
Card, Card,
Checkbox, Checkbox,
DatePicker,
Divider, Divider,
Input, Form,
Layout, Layout,
Modal, Modal,
Progress, Progress,
...@@ -437,21 +436,43 @@ const LogsTable = () => { ...@@ -437,21 +436,43 @@ const LogsTable = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [logCount, setLogCount] = useState(ITEMS_PER_PAGE); const [logCount, setLogCount] = useState(ITEMS_PER_PAGE);
const [logType] = useState(0);
let now = new Date(); let now = new Date();
// 初始化start_timestamp为前一天 // 初始化start_timestamp为前一天
let zeroNow = new Date(now.getFullYear(), now.getMonth(), now.getDate()); let zeroNow = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const [inputs, setInputs] = useState({
// Form 初始值
const formInitValues = {
channel_id: '', channel_id: '',
task_id: '', task_id: '',
start_timestamp: timestamp2string(zeroNow.getTime() / 1000), dateRange: [
end_timestamp: '', timestamp2string(zeroNow.getTime() / 1000),
}); timestamp2string(now.getTime() / 1000 + 3600)
const { channel_id, task_id, start_timestamp, end_timestamp } = inputs; ],
};
// Form API 引用
const [formApi, setFormApi] = useState(null);
const handleInputChange = (value, name) => { // 获取表单值的辅助函数
setInputs((inputs) => ({ ...inputs, [name]: value })); 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) => { const setLogsFormat = (logs) => {
...@@ -469,6 +490,7 @@ const LogsTable = () => { ...@@ -469,6 +490,7 @@ const LogsTable = () => {
setLoading(true); setLoading(true);
let url = ''; let url = '';
const { channel_id, task_id, start_timestamp, end_timestamp } = getFormValues();
let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000); let localStartTimestamp = parseInt(Date.parse(start_timestamp) / 1000);
let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000); let localEndTimestamp = parseInt(Date.parse(end_timestamp) / 1000);
if (isAdminUser) { if (isAdminUser) {
...@@ -528,7 +550,7 @@ const LogsTable = () => { ...@@ -528,7 +550,7 @@ const LogsTable = () => {
const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE; const localPageSize = parseInt(localStorage.getItem('task-page-size')) || ITEMS_PER_PAGE;
setPageSize(localPageSize); setPageSize(localPageSize);
loadLogs(0, localPageSize).then(); loadLogs(0, localPageSize).then();
}, [logType]); }, []);
// 列选择器模态框 // 列选择器模态框
const renderColumnSelector = () => { const renderColumnSelector = () => {
...@@ -628,70 +650,93 @@ const LogsTable = () => { ...@@ -628,70 +650,93 @@ const LogsTable = () => {
<Divider margin="12px" /> <Divider margin="12px" />
{/* 搜索表单区域 */} {/* 搜索表单区域 */}
<div className="flex flex-col gap-4"> <Form
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4"> initValues={formInitValues}
{/* 时间选择器 */} getFormApi={(api) => setFormApi(api)}
<div className="col-span-1 lg:col-span-2"> onSubmit={refresh}
<DatePicker allowEmpty={true}
className="w-full" autoComplete="off"
value={[start_timestamp, end_timestamp]} layout="vertical"
type='dateTimeRange' trigger="change"
onChange={(value) => { stopValidateWithError={false}
if (Array.isArray(value) && value.length === 2) { >
handleInputChange(value[0], 'start_timestamp'); <div className="flex flex-col gap-4">
handleInputChange(value[1], 'end_timestamp'); <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
</div> field='dateRange'
className="w-full"
{/* 任务 ID */} type='dateTimeRange'
<Input placeholder={[t('开始时间'), t('结束时间')]}
prefix={<IconSearch />} showClear
placeholder={t('任务 ID')} pure
value={task_id} />
onChange={(value) => handleInputChange(value, 'task_id')} </div>
className="!rounded-full"
showClear {/* 任务 ID */}
/> <Form.Input
field='task_id'
{/* 渠道 ID - 仅管理员可见 */}
{isAdminUser && (
<Input
prefix={<IconSearch />} prefix={<IconSearch />}
placeholder={t('渠道 ID')} placeholder={t('任务 ID')}
value={channel_id}
onChange={(value) => handleInputChange(value, 'channel_id')}
className="!rounded-full" className="!rounded-full"
showClear showClear
pure
/> />
)}
</div>
{/* 操作按钮区域 */} {/* 渠道 ID - 仅管理员可见 */}
<div className="flex justify-between items-center pt-2"> {isAdminUser && (
<div></div> <Form.Input
<div className="flex gap-2"> field='channel_id'
<Button prefix={<IconSearch />}
type='primary' placeholder={t('渠道 ID')}
onClick={refresh} className="!rounded-full"
loading={loading} showClear
className="!rounded-full" pure
> />
{t('查询')} )}
</Button> </div>
<Button
theme='light' {/* 操作按钮区域 */}
type='tertiary' <div className="flex justify-between items-center pt-2">
icon={<IconSetting />} <div></div>
onClick={() => setShowColumnSelector(true)} <div className="flex gap-2">
className="!rounded-full" <Button
> type='primary'
{t('列设置')} htmlType='submit'
</Button> 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> </div>
</div> </Form>
</div> </div>
} }
shadows='always' shadows='always'
......
...@@ -14,12 +14,12 @@ import { ...@@ -14,12 +14,12 @@ import {
Button, Button,
Card, Card,
Dropdown, Dropdown,
Form,
Modal, Modal,
Space, Space,
SplitButtonGroup, SplitButtonGroup,
Table, Table,
Tag, Tag,
Input,
} from '@douyinfe/semi-ui'; } from '@douyinfe/semi-ui';
import { import {
...@@ -335,14 +335,29 @@ const TokensTable = () => { ...@@ -335,14 +335,29 @@ const TokensTable = () => {
const [tokenCount, setTokenCount] = useState(pageSize); const [tokenCount, setTokenCount] = useState(pageSize);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [searchKeyword, setSearchKeyword] = useState('');
const [searchToken, setSearchToken] = useState('');
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [chats, setChats] = useState([]);
const [editingToken, setEditingToken] = useState({ const [editingToken, setEditingToken] = useState({
id: undefined, 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 = () => { const closeEdit = () => {
setShowEdit(false); setShowEdit(false);
setTimeout(() => { setTimeout(() => {
...@@ -416,8 +431,6 @@ const TokensTable = () => { ...@@ -416,8 +431,6 @@ const TokensTable = () => {
window.open(url, '_blank'); window.open(url, '_blank');
}; };
useEffect(() => { useEffect(() => {
loadTokens(0) loadTokens(0)
.then() .then()
...@@ -472,6 +485,7 @@ const TokensTable = () => { ...@@ -472,6 +485,7 @@ const TokensTable = () => {
}; };
const searchTokens = async () => { const searchTokens = async () => {
const { searchKeyword, searchToken } = getFormValues();
if (searchKeyword === '' && searchToken === '') { if (searchKeyword === '' && searchToken === '') {
await loadTokens(0); await loadTokens(0);
setActivePage(1); setActivePage(1);
...@@ -491,14 +505,6 @@ const TokensTable = () => { ...@@ -491,14 +505,6 @@ const TokensTable = () => {
setSearching(false); setSearching(false);
}; };
const handleKeywordChange = async (value) => {
setSearchKeyword(value.trim());
};
const handleSearchTokenChange = async (value) => {
setSearchToken(value.trim());
};
const sortToken = (key) => { const sortToken = (key) => {
if (tokens.length === 0) return; if (tokens.length === 0) return;
setLoading(true); setLoading(true);
...@@ -580,36 +586,65 @@ const TokensTable = () => { ...@@ -580,36 +586,65 @@ const TokensTable = () => {
</Button> </Button>
</div> </div>
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2"> <Form
<div className="relative w-full md:w-56"> initValues={formInitValues}
<Input getFormApi={(api) => setFormApi(api)}
prefix={<IconSearch />} onSubmit={searchTokens}
placeholder={t('搜索关键字')} allowEmpty={true}
value={searchKeyword} autoComplete="off"
onChange={handleKeywordChange} layout="horizontal"
className="!rounded-full" trigger="change"
showClear stopValidateWithError={false}
/> className="w-full md:w-auto order-1 md:order-2"
</div> >
<div className="relative w-full md:w-56"> <div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
<Input <div className="relative w-full md:w-56">
prefix={<IconSearch />} <Form.Input
placeholder={t('密钥')} field="searchKeyword"
value={searchToken} prefix={<IconSearch />}
onChange={handleSearchTokenChange} placeholder={t('搜索关键字')}
className="!rounded-full" className="!rounded-full"
showClear 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>
<Button </Form>
type="primary"
onClick={searchTokens}
loading={searching}
className="!rounded-full w-full md:w-auto"
>
{t('查询')}
</Button>
</div>
</div> </div>
</div> </div>
); );
......
...@@ -5,9 +5,8 @@ import { ...@@ -5,9 +5,8 @@ import {
Card, Card,
Divider, Divider,
Dropdown, Dropdown,
Input, Form,
Modal, Modal,
Select,
Space, Space,
Table, Table,
Tag, Tag,
...@@ -285,9 +284,7 @@ const UsersTable = () => { ...@@ -285,9 +284,7 @@ const UsersTable = () => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [activePage, setActivePage] = useState(1); const [activePage, setActivePage] = useState(1);
const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE); const [pageSize, setPageSize] = useState(ITEMS_PER_PAGE);
const [searchKeyword, setSearchKeyword] = useState('');
const [searching, setSearching] = useState(false); const [searching, setSearching] = useState(false);
const [searchGroup, setSearchGroup] = useState('');
const [groupOptions, setGroupOptions] = useState([]); const [groupOptions, setGroupOptions] = useState([]);
const [userCount, setUserCount] = useState(ITEMS_PER_PAGE); const [userCount, setUserCount] = useState(ITEMS_PER_PAGE);
const [showAddUser, setShowAddUser] = useState(false); const [showAddUser, setShowAddUser] = useState(false);
...@@ -296,6 +293,24 @@ const UsersTable = () => { ...@@ -296,6 +293,24 @@ const UsersTable = () => {
id: undefined, 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) => { const removeRecord = (key) => {
let newDataSource = [...users]; let newDataSource = [...users];
if (key != null) { if (key != null) {
...@@ -363,9 +378,16 @@ const UsersTable = () => { ...@@ -363,9 +378,16 @@ const UsersTable = () => {
const searchUsers = async ( const searchUsers = async (
startIdx, startIdx,
pageSize, pageSize,
searchKeyword, searchKeyword = null,
searchGroup, searchGroup = null,
) => { ) => {
// 如果没有传递参数,从表单获取值
if (searchKeyword === null || searchGroup === null) {
const formValues = getFormValues();
searchKeyword = formValues.searchKeyword;
searchGroup = formValues.searchGroup;
}
if (searchKeyword === '' && searchGroup === '') { if (searchKeyword === '' && searchGroup === '') {
// if keyword is blank, load files instead. // if keyword is blank, load files instead.
await loadUsers(startIdx, pageSize); await loadUsers(startIdx, pageSize);
...@@ -387,12 +409,9 @@ const UsersTable = () => { ...@@ -387,12 +409,9 @@ const UsersTable = () => {
setSearching(false); setSearching(false);
}; };
const handleKeywordChange = async (value) => {
setSearchKeyword(value.trim());
};
const handlePageChange = (page) => { const handlePageChange = (page) => {
setActivePage(page); setActivePage(page);
const { searchKeyword, searchGroup } = getFormValues();
if (searchKeyword === '' && searchGroup === '') { if (searchKeyword === '' && searchGroup === '') {
loadUsers(page, pageSize).then(); loadUsers(page, pageSize).then();
} else { } else {
...@@ -413,10 +432,11 @@ const UsersTable = () => { ...@@ -413,10 +432,11 @@ const UsersTable = () => {
const refresh = async () => { const refresh = async () => {
setActivePage(1); setActivePage(1);
if (searchKeyword === '') { const { searchKeyword, searchGroup } = getFormValues();
await loadUsers(activePage, pageSize); if (searchKeyword === '' && searchGroup === '') {
await loadUsers(1, pageSize);
} else { } else {
await searchUsers(activePage, pageSize, searchKeyword, searchGroup); await searchUsers(1, pageSize, searchKeyword, searchGroup);
} }
}; };
...@@ -488,41 +508,76 @@ const UsersTable = () => { ...@@ -488,41 +508,76 @@ const UsersTable = () => {
</Button> </Button>
</div> </div>
<div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto order-1 md:order-2"> <Form
<div className="relative w-full md:w-64"> initValues={formInitValues}
<Input getFormApi={(api) => setFormApi(api)}
prefix={<IconSearch />} onSubmit={() => {
placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')} setActivePage(1);
value={searchKeyword} searchUsers(1, pageSize);
onChange={handleKeywordChange} }}
className="!rounded-full" allowEmpty={true}
showClear autoComplete="off"
/> layout="horizontal"
</div> trigger="change"
<div className="w-full md:w-48"> stopValidateWithError={false}
<Select className="w-full md:w-auto order-1 md:order-2"
placeholder={t('选择分组')} >
optionList={groupOptions} <div className="flex flex-col md:flex-row items-center gap-4 w-full md:w-auto">
value={searchGroup} <div className="relative w-full md:w-64">
onChange={(value) => { <Form.Input
setSearchGroup(value); field="searchKeyword"
searchUsers(activePage, pageSize, searchKeyword, value); prefix={<IconSearch />}
}} placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')}
className="!rounded-full w-full" className="!rounded-full"
showClear 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>
<Button </Form>
type="primary"
onClick={() => {
searchUsers(activePage, pageSize, searchKeyword, searchGroup);
}}
loading={searching}
className="!rounded-full w-full md:w-auto"
>
{t('查询')}
</Button>
</div>
</div> </div>
</div> </div>
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment