|
|
@@ -1,92 +1,116 @@
|
|
|
-import React from 'react';
|
|
|
+import React, { useState } from 'react';
|
|
|
import {
|
|
|
Modal,
|
|
|
Transfer,
|
|
|
Input,
|
|
|
Space,
|
|
|
Checkbox,
|
|
|
+ Avatar,
|
|
|
+ Highlight,
|
|
|
} from '@douyinfe/semi-ui';
|
|
|
import { IconClose } from '@douyinfe/semi-icons';
|
|
|
|
|
|
-/**
|
|
|
- * ChannelSelectorModal
|
|
|
- * 负责选择同步渠道、测试与批量测试等 UI,纯展示组件。
|
|
|
- * 业务状态与动作通过 props 注入,保持可复用与可测试。
|
|
|
- */
|
|
|
+const CHANNEL_STATUS_CONFIG = {
|
|
|
+ 1: { color: 'green', text: '启用' },
|
|
|
+ 2: { color: 'red', text: '禁用' },
|
|
|
+ 3: { color: 'amber', text: '自禁' },
|
|
|
+ default: { color: 'grey', text: '未知' }
|
|
|
+};
|
|
|
+
|
|
|
+const getChannelStatusConfig = (status) => {
|
|
|
+ return CHANNEL_STATUS_CONFIG[status] || CHANNEL_STATUS_CONFIG.default;
|
|
|
+};
|
|
|
+
|
|
|
export default function ChannelSelectorModal({
|
|
|
t,
|
|
|
visible,
|
|
|
onCancel,
|
|
|
onOk,
|
|
|
- // 渠道选择
|
|
|
allChannels = [],
|
|
|
selectedChannelIds = [],
|
|
|
setSelectedChannelIds,
|
|
|
- // 渠道端点
|
|
|
channelEndpoints,
|
|
|
updateChannelEndpoint,
|
|
|
}) {
|
|
|
- // Transfer 自定义渲染
|
|
|
- const renderSourceItem = (item) => {
|
|
|
+ const [searchText, setSearchText] = useState('');
|
|
|
+
|
|
|
+ const ChannelInfo = ({ item, showEndpoint = false, isSelected = false }) => {
|
|
|
const channelId = item.key || item.value;
|
|
|
const currentEndpoint = channelEndpoints[channelId];
|
|
|
const baseUrl = item._originalData?.base_url || '';
|
|
|
+ const status = item._originalData?.status || 0;
|
|
|
+ const statusConfig = getChannelStatusConfig(status);
|
|
|
|
|
|
return (
|
|
|
- <div key={item.key} style={{ padding: 8 }}>
|
|
|
- <div className="flex flex-col gap-2 w-full">
|
|
|
- <div className="flex items-center w-full">
|
|
|
- <Checkbox checked={item.checked} onChange={item.onChange}>
|
|
|
- <span className="font-medium">{item.label}</span>
|
|
|
- </Checkbox>
|
|
|
+ <>
|
|
|
+ <Avatar color={statusConfig.color} size="small">
|
|
|
+ {statusConfig.text}
|
|
|
+ </Avatar>
|
|
|
+ <div className="info">
|
|
|
+ <div className="name">
|
|
|
+ {isSelected ? (
|
|
|
+ item.label
|
|
|
+ ) : (
|
|
|
+ <Highlight sourceString={item.label} searchWords={[searchText]} />
|
|
|
+ )}
|
|
|
</div>
|
|
|
- <div className="flex items-center gap-1 ml-4">
|
|
|
- <span className="text-xs text-gray-500 truncate max-w-[120px]" title={baseUrl}>
|
|
|
- {baseUrl}
|
|
|
+ <div className="email" style={showEndpoint ? { display: 'flex', alignItems: 'center', gap: '4px' } : {}}>
|
|
|
+ <span className="text-xs text-gray-500 truncate max-w-[200px]" title={baseUrl}>
|
|
|
+ {isSelected ? (
|
|
|
+ baseUrl
|
|
|
+ ) : (
|
|
|
+ <Highlight sourceString={baseUrl} searchWords={[searchText]} />
|
|
|
+ )}
|
|
|
</span>
|
|
|
- <Input
|
|
|
- size="small"
|
|
|
- value={currentEndpoint}
|
|
|
- onChange={(value) => updateChannelEndpoint(channelId, value)}
|
|
|
- placeholder="/api/ratio_config"
|
|
|
- className="flex-1 text-xs"
|
|
|
- style={{ fontSize: '12px' }}
|
|
|
- />
|
|
|
+ {showEndpoint && (
|
|
|
+ <Input
|
|
|
+ size="small"
|
|
|
+ value={currentEndpoint}
|
|
|
+ onChange={(value) => updateChannelEndpoint(channelId, value)}
|
|
|
+ placeholder="/api/ratio_config"
|
|
|
+ className="flex-1 text-xs"
|
|
|
+ style={{ fontSize: '12px' }}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ {isSelected && !showEndpoint && (
|
|
|
+ <span className="text-xs text-gray-700 font-mono bg-gray-100 px-2 py-1 rounded ml-2">
|
|
|
+ {currentEndpoint}
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ const renderSourceItem = (item) => {
|
|
|
+ return (
|
|
|
+ <div className="components-transfer-source-item" key={item.key}>
|
|
|
+ <Checkbox
|
|
|
+ onChange={item.onChange}
|
|
|
+ checked={item.checked}
|
|
|
+ style={{ height: 52, alignItems: 'center' }}
|
|
|
+ >
|
|
|
+ <ChannelInfo item={item} showEndpoint={true} />
|
|
|
+ </Checkbox>
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
const renderSelectedItem = (item) => {
|
|
|
- const channelId = item.key || item.value;
|
|
|
- const currentEndpoint = channelEndpoints[channelId];
|
|
|
- const baseUrl = item._originalData?.base_url || '';
|
|
|
-
|
|
|
return (
|
|
|
- <div key={item.key} style={{ padding: 6 }}>
|
|
|
- <div className="flex flex-col gap-2 w-full">
|
|
|
- <div className="flex items-center w-full">
|
|
|
- <span className="font-medium">{item.label}</span>
|
|
|
- <IconClose style={{ cursor: 'pointer' }} onClick={item.onRemove} className="ml-auto" />
|
|
|
- </div>
|
|
|
- <div className="flex items-center gap-1 ml-4">
|
|
|
- <span
|
|
|
- className="text-xs text-gray-500 truncate max-w-[120px]"
|
|
|
- title={baseUrl}
|
|
|
- >
|
|
|
- {baseUrl}
|
|
|
- </span>
|
|
|
- <span className="text-xs text-gray-700 font-mono bg-gray-100 px-2 py-1 rounded flex-1">
|
|
|
- {currentEndpoint}
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+ <div className="components-transfer-selected-item" key={item.key}>
|
|
|
+ <ChannelInfo item={item} isSelected={true} />
|
|
|
+ <IconClose style={{ cursor: 'pointer' }} onClick={item.onRemove} />
|
|
|
</div>
|
|
|
);
|
|
|
};
|
|
|
|
|
|
- const channelFilter = (input, item) => item.label.toLowerCase().includes(input.toLowerCase());
|
|
|
+ const channelFilter = (input, item) => {
|
|
|
+ const searchLower = input.toLowerCase();
|
|
|
+ return item.label.toLowerCase().includes(searchLower) ||
|
|
|
+ (item._originalData?.base_url || '').toLowerCase().includes(searchLower);
|
|
|
+ };
|
|
|
|
|
|
return (
|
|
|
<Modal
|
|
|
@@ -106,6 +130,7 @@ export default function ChannelSelectorModal({
|
|
|
renderSelectedItem={renderSelectedItem}
|
|
|
filter={channelFilter}
|
|
|
inputProps={{ placeholder: t('搜索渠道名称或地址') }}
|
|
|
+ onSearch={setSearchText}
|
|
|
emptyContent={{
|
|
|
left: t('暂无渠道'),
|
|
|
right: t('暂无选择'),
|