🚀 feat(ratio-sync): major refactor & UX overhaul for Upstream Ratio Sync

This commit is contained in:
Apple\Apple
2025-06-19 08:57:34 +08:00
parent a180d13182
commit 7975cdf3bf
16 changed files with 1319 additions and 11 deletions

View File

@@ -0,0 +1,154 @@
import React from 'react';
import {
Modal,
Transfer,
Input,
Card,
Space,
Button,
Checkbox,
} from '@douyinfe/semi-ui';
import { IconPlus, IconClose } from '@douyinfe/semi-icons';
/**
* ChannelSelectorModal
* 负责选择同步渠道、测试与批量测试等 UI纯展示组件。
* 业务状态与动作通过 props 注入,保持可复用与可测试。
*/
export default function ChannelSelectorModal({
t,
visible,
onCancel,
onOk,
// 渠道与选择
allChannels = [],
selectedChannelIds = [],
setSelectedChannelIds,
// 自定义渠道
customUrl,
setCustomUrl,
customEndpoint,
setCustomEndpoint,
customChannelTesting,
addCustomChannel,
// 渠道端点
channelEndpoints,
updateChannelEndpoint,
// 测试相关
}) {
// Transfer 自定义渲染
const renderSourceItem = (item) => {
const channelId = item.key || item.value;
const currentEndpoint = channelEndpoints[channelId];
const baseUrl = item._originalData?.base_url || '';
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>
</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>
<Input
size="small"
value={currentEndpoint}
onChange={(value) => updateChannelEndpoint(channelId, value)}
placeholder="/api/ratio_config"
className="flex-1 text-xs"
style={{ fontSize: '12px' }}
/>
</div>
</div>
</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>
);
};
const channelFilter = (input, item) => item.label.toLowerCase().includes(input.toLowerCase());
return (
<Modal
visible={visible}
onCancel={onCancel}
onOk={onOk}
title={<span className="text-lg font-semibold">{t('选择同步渠道')}</span>}
width={1000}
>
<Space vertical style={{ width: '100%' }}>
<Card title={t('添加自定义渠道')} className="w-full">
<Space direction="horizontal" style={{ width: '100%' }}>
<Input
placeholder={t('渠道地址https://example.com')}
value={customUrl}
onChange={setCustomUrl}
style={{ flex: 1 }}
/>
<Input
placeholder={t('接口路径')}
value={customEndpoint}
onChange={setCustomEndpoint}
style={{ width: 150 }}
/>
<Button
icon={<IconPlus />}
onClick={addCustomChannel}
loading={customChannelTesting}
disabled={!customUrl}
className="whitespace-nowrap"
>
{customChannelTesting ? t('测试中...') : t('添加')}
</Button>
</Space>
</Card>
<Transfer
style={{ width: '100%' }}
dataSource={allChannels}
value={selectedChannelIds}
onChange={setSelectedChannelIds}
renderSourceItem={renderSourceItem}
renderSelectedItem={renderSelectedItem}
filter={channelFilter}
inputProps={{ placeholder: t('搜索渠道名称或地址') }}
emptyContent={{
left: t('暂无渠道'),
right: t('暂无选择'),
search: t('无搜索结果'),
}}
/>
</Space>
</Modal>
);
}