|
|
@@ -6,15 +6,31 @@ import {
|
|
|
showSuccess,
|
|
|
timestamp2string,
|
|
|
renderGroup,
|
|
|
- renderQuotaWithAmount,
|
|
|
- renderQuota
|
|
|
+ renderNumberWithPoint,
|
|
|
+ renderQuota,
|
|
|
+ getChannelIcon
|
|
|
} from '../../helpers/index.js';
|
|
|
|
|
|
+import {
|
|
|
+ CheckCircle,
|
|
|
+ XCircle,
|
|
|
+ AlertCircle,
|
|
|
+ HelpCircle,
|
|
|
+ TestTube,
|
|
|
+ Zap,
|
|
|
+ Timer,
|
|
|
+ Clock,
|
|
|
+ AlertTriangle,
|
|
|
+ Coins,
|
|
|
+ Tags
|
|
|
+} from 'lucide-react';
|
|
|
+
|
|
|
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../../constants/index.js';
|
|
|
import {
|
|
|
Button,
|
|
|
Divider,
|
|
|
Dropdown,
|
|
|
+ Empty,
|
|
|
Input,
|
|
|
InputNumber,
|
|
|
Modal,
|
|
|
@@ -25,13 +41,15 @@ import {
|
|
|
Tag,
|
|
|
Tooltip,
|
|
|
Typography,
|
|
|
- Checkbox,
|
|
|
Card,
|
|
|
- Select
|
|
|
+ Form
|
|
|
} from '@douyinfe/semi-ui';
|
|
|
+import {
|
|
|
+ IllustrationNoResult,
|
|
|
+ IllustrationNoResultDark
|
|
|
+} from '@douyinfe/semi-illustrations';
|
|
|
import EditChannel from '../../pages/Channel/EditChannel.js';
|
|
|
import {
|
|
|
- IconList,
|
|
|
IconTreeTriangleDown,
|
|
|
IconFilter,
|
|
|
IconPlus,
|
|
|
@@ -64,7 +82,12 @@ const ChannelsTable = () => {
|
|
|
type2label[0] = { value: 0, label: t('未知类型'), color: 'grey' };
|
|
|
}
|
|
|
return (
|
|
|
- <Tag size='large' color={type2label[type]?.color} shape='circle'>
|
|
|
+ <Tag
|
|
|
+ size='large'
|
|
|
+ color={type2label[type]?.color}
|
|
|
+ shape='circle'
|
|
|
+ prefixIcon={getChannelIcon(type)}
|
|
|
+ >
|
|
|
{type2label[type]?.label}
|
|
|
</Tag>
|
|
|
);
|
|
|
@@ -74,7 +97,7 @@ const ChannelsTable = () => {
|
|
|
return (
|
|
|
<Tag
|
|
|
color='light-blue'
|
|
|
- prefixIcon={<IconList />}
|
|
|
+ prefixIcon={<Tags size={14} />}
|
|
|
size='large'
|
|
|
shape='circle'
|
|
|
type='light'
|
|
|
@@ -88,25 +111,25 @@ const ChannelsTable = () => {
|
|
|
switch (status) {
|
|
|
case 1:
|
|
|
return (
|
|
|
- <Tag size='large' color='green' shape='circle'>
|
|
|
+ <Tag size='large' color='green' shape='circle' prefixIcon={<CheckCircle size={14} />}>
|
|
|
{t('已启用')}
|
|
|
</Tag>
|
|
|
);
|
|
|
case 2:
|
|
|
return (
|
|
|
- <Tag size='large' color='yellow' shape='circle'>
|
|
|
+ <Tag size='large' color='yellow' shape='circle' prefixIcon={<XCircle size={14} />}>
|
|
|
{t('已禁用')}
|
|
|
</Tag>
|
|
|
);
|
|
|
case 3:
|
|
|
return (
|
|
|
- <Tag size='large' color='yellow' shape='circle'>
|
|
|
+ <Tag size='large' color='yellow' shape='circle' prefixIcon={<AlertCircle size={14} />}>
|
|
|
{t('自动禁用')}
|
|
|
</Tag>
|
|
|
);
|
|
|
default:
|
|
|
return (
|
|
|
- <Tag size='large' color='grey' shape='circle'>
|
|
|
+ <Tag size='large' color='grey' shape='circle' prefixIcon={<HelpCircle size={14} />}>
|
|
|
{t('未知状态')}
|
|
|
</Tag>
|
|
|
);
|
|
|
@@ -118,139 +141,48 @@ const ChannelsTable = () => {
|
|
|
time = time.toFixed(2) + t(' 秒');
|
|
|
if (responseTime === 0) {
|
|
|
return (
|
|
|
- <Tag size='large' color='grey' shape='circle'>
|
|
|
+ <Tag size='large' color='grey' shape='circle' prefixIcon={<TestTube size={14} />}>
|
|
|
{t('未测试')}
|
|
|
</Tag>
|
|
|
);
|
|
|
} else if (responseTime <= 1000) {
|
|
|
return (
|
|
|
- <Tag size='large' color='green' shape='circle'>
|
|
|
+ <Tag size='large' color='green' shape='circle' prefixIcon={<Zap size={14} />}>
|
|
|
{time}
|
|
|
</Tag>
|
|
|
);
|
|
|
} else if (responseTime <= 3000) {
|
|
|
return (
|
|
|
- <Tag size='large' color='lime' shape='circle'>
|
|
|
+ <Tag size='large' color='lime' shape='circle' prefixIcon={<Timer size={14} />}>
|
|
|
{time}
|
|
|
</Tag>
|
|
|
);
|
|
|
} else if (responseTime <= 5000) {
|
|
|
return (
|
|
|
- <Tag size='large' color='yellow' shape='circle'>
|
|
|
+ <Tag size='large' color='yellow' shape='circle' prefixIcon={<Clock size={14} />}>
|
|
|
{time}
|
|
|
</Tag>
|
|
|
);
|
|
|
} else {
|
|
|
return (
|
|
|
- <Tag size='large' color='red' shape='circle'>
|
|
|
+ <Tag size='large' color='red' shape='circle' prefixIcon={<AlertTriangle size={14} />}>
|
|
|
{time}
|
|
|
</Tag>
|
|
|
);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- // 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 +201,6 @@ const ChannelsTable = () => {
|
|
|
),
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.TYPE,
|
|
|
title: t('类型'),
|
|
|
dataIndex: 'type',
|
|
|
render: (text, record, index) => {
|
|
|
@@ -281,7 +212,6 @@ const ChannelsTable = () => {
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.STATUS,
|
|
|
title: t('状态'),
|
|
|
dataIndex: 'status',
|
|
|
render: (text, record, index) => {
|
|
|
@@ -307,7 +237,6 @@ const ChannelsTable = () => {
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.RESPONSE_TIME,
|
|
|
title: t('响应时间'),
|
|
|
dataIndex: 'response_time',
|
|
|
render: (text, record, index) => (
|
|
|
@@ -315,7 +244,6 @@ const ChannelsTable = () => {
|
|
|
),
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.BALANCE,
|
|
|
title: t('已用/剩余'),
|
|
|
dataIndex: 'expired_time',
|
|
|
render: (text, record, index) => {
|
|
|
@@ -324,7 +252,7 @@ const ChannelsTable = () => {
|
|
|
<div>
|
|
|
<Space spacing={1}>
|
|
|
<Tooltip content={t('已用额度')}>
|
|
|
- <Tag color='white' type='ghost' size='large' shape='circle'>
|
|
|
+ <Tag color='white' type='ghost' size='large' shape='circle' prefixIcon={<Coins size={14} />}>
|
|
|
{renderQuota(record.used_quota)}
|
|
|
</Tag>
|
|
|
</Tooltip>
|
|
|
@@ -334,6 +262,7 @@ const ChannelsTable = () => {
|
|
|
type='ghost'
|
|
|
size='large'
|
|
|
shape='circle'
|
|
|
+ prefixIcon={<Coins size={14} />}
|
|
|
onClick={() => updateChannelBalance(record)}
|
|
|
>
|
|
|
{renderQuotaWithAmount(record.balance)}
|
|
|
@@ -345,7 +274,7 @@ const ChannelsTable = () => {
|
|
|
} else {
|
|
|
return (
|
|
|
<Tooltip content={t('已用额度')}>
|
|
|
- <Tag color='white' type='ghost' size='large' shape='circle'>
|
|
|
+ <Tag color='white' type='ghost' size='large' shape='circle' prefixIcon={<Coins size={14} />}>
|
|
|
{renderQuota(record.used_quota)}
|
|
|
</Tag>
|
|
|
</Tooltip>
|
|
|
@@ -354,7 +283,6 @@ const ChannelsTable = () => {
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.PRIORITY,
|
|
|
title: t('优先级'),
|
|
|
dataIndex: 'priority',
|
|
|
render: (text, record, index) => {
|
|
|
@@ -406,7 +334,6 @@ const ChannelsTable = () => {
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.WEIGHT,
|
|
|
title: t('权重'),
|
|
|
dataIndex: 'weight',
|
|
|
render: (text, record, index) => {
|
|
|
@@ -458,7 +385,6 @@ const ChannelsTable = () => {
|
|
|
},
|
|
|
},
|
|
|
{
|
|
|
- key: COLUMN_KEYS.OPERATE,
|
|
|
title: '',
|
|
|
dataIndex: 'operate',
|
|
|
fixed: 'right',
|
|
|
@@ -631,96 +557,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 +585,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 +746,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,29 +856,40 @@ const ChannelsTable = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const searchChannels = async (
|
|
|
- searchKeyword,
|
|
|
- searchGroup,
|
|
|
- searchModel,
|
|
|
- enableTagMode,
|
|
|
- ) => {
|
|
|
- if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
|
|
- await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
|
|
|
- // setActivePage(1);
|
|
|
- return;
|
|
|
- }
|
|
|
+ // 获取表单值的辅助函数,确保所有值都是字符串
|
|
|
+ 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();
|
|
|
+
|
|
|
setSearching(true);
|
|
|
- const res = await API.get(
|
|
|
- `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}&tag_mode=${enableTagMode}`,
|
|
|
- );
|
|
|
- const { success, message, data } = res.data;
|
|
|
- if (success) {
|
|
|
- setChannelFormat(data, enableTagMode);
|
|
|
- setActivePage(1);
|
|
|
- } else {
|
|
|
- showError(message);
|
|
|
+ try {
|
|
|
+ if (searchKeyword === '' && searchGroup === '' && searchModel === '') {
|
|
|
+ await loadChannels(activePage - 1, pageSize, idSort, enableTagMode);
|
|
|
+ // setActivePage(1);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const res = await API.get(
|
|
|
+ `/api/channel/search?keyword=${searchKeyword}&group=${searchGroup}&model=${searchModel}&id_sort=${idSort}&tag_mode=${enableTagMode}`,
|
|
|
+ );
|
|
|
+ const { success, message, data } = res.data;
|
|
|
+ if (success) {
|
|
|
+ setChannelFormat(data, enableTagMode);
|
|
|
+ setActivePage(1);
|
|
|
+ } else {
|
|
|
+ showError(message);
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setSearching(false);
|
|
|
}
|
|
|
- setSearching(false);
|
|
|
};
|
|
|
|
|
|
const updateChannelProperty = (channelId, updateFn) => {
|
|
|
@@ -1540,71 +1397,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={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 +1481,6 @@ const ChannelsTable = () => {
|
|
|
|
|
|
return (
|
|
|
<>
|
|
|
- {renderColumnSelector()}
|
|
|
<EditTagModal
|
|
|
visible={showEditTag}
|
|
|
tag={editingTag}
|
|
|
@@ -1633,7 +1501,7 @@ const ChannelsTable = () => {
|
|
|
bordered={false}
|
|
|
>
|
|
|
<Table
|
|
|
- columns={getVisibleColumns()}
|
|
|
+ columns={columns}
|
|
|
dataSource={pageData}
|
|
|
scroll={{ x: 'max-content' }}
|
|
|
pagination={{
|
|
|
@@ -1663,6 +1531,14 @@ const ChannelsTable = () => {
|
|
|
}
|
|
|
: null
|
|
|
}
|
|
|
+ empty={
|
|
|
+ <Empty
|
|
|
+ image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
|
|
+ darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
|
|
|
+ description={t('搜索无结果')}
|
|
|
+ style={{ padding: 30 }}
|
|
|
+ />
|
|
|
+ }
|
|
|
className="rounded-xl overflow-hidden"
|
|
|
size="middle"
|
|
|
loading={loading}
|