diff --git a/web/src/components/table/ChannelsTable.js b/web/src/components/table/ChannelsTable.js index 105aa217..73840535 100644 --- a/web/src/components/table/ChannelsTable.js +++ b/web/src/components/table/ChannelsTable.js @@ -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 ( - setShowColumnSelector(false)} - footer={ -
- - - -
- } - size="middle" - centered={true} - > -
- v === true)} - indeterminate={ - Object.values(visibleColumns).some((v) => v === true) && - !Object.values(visibleColumns).every((v) => v === true) - } - onChange={(e) => handleSelectAll(e.target.checked)} - > - {t('全选')} - -
-
- {allColumns.map((column) => { - // Skip columns without title - if (!column.title) { - return null; - } - - return ( -
- - handleColumnVisibilityChange(column.key, e.target.checked) - } - > - {column.title} - -
- ); - })} -
-
- ); - }; - 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('刷新')} - -
-
- } - placeholder={t('搜索渠道的 ID,名称,密钥和API地址 ...')} - value={searchKeyword} - loading={searching} - onChange={(v) => { - setSearchKeyword(v.trim()); - }} - className="!rounded-full" - showClear - /> -
-
- } - placeholder={t('模型关键字')} - value={searchModel} - loading={searching} - onChange={(v) => { - setSearchModel(v.trim()); - }} - className="!rounded-full" - showClear - /> -
-
- } - placeholder={t('任务 ID')} - value={mj_id} - onChange={(value) => handleInputChange(value, 'mj_id')} - className="!rounded-full" - showClear - /> - - {/* 渠道 ID - 仅管理员可见 */} - {isAdminUser && ( - } - placeholder={t('渠道 ID')} - value={channel_id} - onChange={(value) => handleInputChange(value, 'channel_id')} + placeholder={t('任务 ID')} className="!rounded-full" showClear + pure /> - )} -
- {/* 操作按钮区域 */} -
-
-
- - + {/* 渠道 ID - 仅管理员可见 */} + {isAdminUser && ( + } + placeholder={t('渠道 ID')} + className="!rounded-full" + showClear + pure + /> + )} +
+ + {/* 操作按钮区域 */} +
+
+
+ + + +
-
+ } shadows='always' diff --git a/web/src/components/table/RedemptionsTable.js b/web/src/components/table/RedemptionsTable.js index 62ccb7ac..bf8985aa 100644 --- a/web/src/components/table/RedemptionsTable.js +++ b/web/src/components/table/RedemptionsTable.js @@ -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 = () => { -
-
- } - placeholder={t('关键字(id或者名称)')} - value={searchKeyword} - onChange={handleKeywordChange} - className="!rounded-full" - showClear - /> +
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" + > +
+
+ } + placeholder={t('关键字(id或者名称)')} + className="!rounded-full" + showClear + pure + /> +
+
+ + +
- -
+
); @@ -517,6 +566,7 @@ const RedemptionsTable = () => { onPageSizeChange: (size) => { setPageSize(size); setActivePage(1); + const { searchKeyword } = getFormValues(); if (searchKeyword === '') { loadRedemptions(1, size).then(); } else { diff --git a/web/src/components/table/TaskLogsTable.js b/web/src/components/table/TaskLogsTable.js index 91ccc06c..cc8cd6d5 100644 --- a/web/src/components/table/TaskLogsTable.js +++ b/web/src/components/table/TaskLogsTable.js @@ -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 = () => { {/* 搜索表单区域 */} -
-
- {/* 时间选择器 */} -
- { - if (Array.isArray(value) && value.length === 2) { - handleInputChange(value[0], 'start_timestamp'); - handleInputChange(value[1], 'end_timestamp'); - } - }} - /> -
+
setFormApi(api)} + onSubmit={refresh} + allowEmpty={true} + autoComplete="off" + layout="vertical" + trigger="change" + stopValidateWithError={false} + > +
+
+ {/* 时间选择器 */} +
+ +
- {/* 任务 ID */} - } - placeholder={t('任务 ID')} - value={task_id} - onChange={(value) => handleInputChange(value, 'task_id')} - className="!rounded-full" - showClear - /> - - {/* 渠道 ID - 仅管理员可见 */} - {isAdminUser && ( - } - placeholder={t('渠道 ID')} - value={channel_id} - onChange={(value) => handleInputChange(value, 'channel_id')} + placeholder={t('任务 ID')} className="!rounded-full" showClear + pure /> - )} -
- {/* 操作按钮区域 */} -
-
-
- - + {/* 渠道 ID - 仅管理员可见 */} + {isAdminUser && ( + } + placeholder={t('渠道 ID')} + className="!rounded-full" + showClear + pure + /> + )} +
+ + {/* 操作按钮区域 */} +
+
+
+ + + +
-
+
} shadows='always' diff --git a/web/src/components/table/TokensTable.js b/web/src/components/table/TokensTable.js index 7d4f5af0..9d0ec522 100644 --- a/web/src/components/table/TokensTable.js +++ b/web/src/components/table/TokensTable.js @@ -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 = () => {
-
-
- } - placeholder={t('搜索关键字')} - value={searchKeyword} - onChange={handleKeywordChange} - className="!rounded-full" - showClear - /> +
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" + > +
+
+ } + placeholder={t('搜索关键字')} + className="!rounded-full" + showClear + pure + /> +
+
+ } + placeholder={t('密钥')} + className="!rounded-full" + showClear + pure + /> +
+
+ + +
-
- } - placeholder={t('密钥')} - value={searchToken} - onChange={handleSearchTokenChange} - className="!rounded-full" - showClear - /> -
- -
+
); diff --git a/web/src/components/table/UsersTable.js b/web/src/components/table/UsersTable.js index 8c713a1a..247a015a 100644 --- a/web/src/components/table/UsersTable.js +++ b/web/src/components/table/UsersTable.js @@ -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 = () => { -
-
- } - placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')} - value={searchKeyword} - onChange={handleKeywordChange} - className="!rounded-full" - showClear - /> +
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" + > +
+
+ } + placeholder={t('支持搜索用户的 ID、用户名、显示名称和邮箱地址')} + className="!rounded-full" + showClear + pure + /> +
+
+ { + // 分组变化时自动搜索 + setTimeout(() => { + setActivePage(1); + searchUsers(1, pageSize); + }, 100); + }} + className="!rounded-full w-full" + showClear + pure + /> +
+
+ + +
-
-