ChannelSelectorModal.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import React from 'react';
  2. import {
  3. Modal,
  4. Transfer,
  5. Input,
  6. Card,
  7. Space,
  8. Button,
  9. Checkbox,
  10. } from '@douyinfe/semi-ui';
  11. import { IconPlus, IconClose } from '@douyinfe/semi-icons';
  12. /**
  13. * ChannelSelectorModal
  14. * 负责选择同步渠道、测试与批量测试等 UI,纯展示组件。
  15. * 业务状态与动作通过 props 注入,保持可复用与可测试。
  16. */
  17. export default function ChannelSelectorModal({
  18. t,
  19. visible,
  20. onCancel,
  21. onOk,
  22. // 渠道与选择
  23. allChannels = [],
  24. selectedChannelIds = [],
  25. setSelectedChannelIds,
  26. // 自定义渠道
  27. customUrl,
  28. setCustomUrl,
  29. customEndpoint,
  30. setCustomEndpoint,
  31. customChannelTesting,
  32. addCustomChannel,
  33. // 渠道端点
  34. channelEndpoints,
  35. updateChannelEndpoint,
  36. // 测试相关
  37. }) {
  38. // Transfer 自定义渲染
  39. const renderSourceItem = (item) => {
  40. const channelId = item.key || item.value;
  41. const currentEndpoint = channelEndpoints[channelId];
  42. const baseUrl = item._originalData?.base_url || '';
  43. return (
  44. <div key={item.key} style={{ padding: 8 }}>
  45. <div className="flex flex-col gap-2 w-full">
  46. <div className="flex items-center w-full">
  47. <Checkbox checked={item.checked} onChange={item.onChange}>
  48. <span className="font-medium">{item.label}</span>
  49. </Checkbox>
  50. </div>
  51. <div className="flex items-center gap-1 ml-4">
  52. <span className="text-xs text-gray-500 truncate max-w-[120px]" title={baseUrl}>
  53. {baseUrl}
  54. </span>
  55. <Input
  56. size="small"
  57. value={currentEndpoint}
  58. onChange={(value) => updateChannelEndpoint(channelId, value)}
  59. placeholder="/api/ratio_config"
  60. className="flex-1 text-xs"
  61. style={{ fontSize: '12px' }}
  62. />
  63. </div>
  64. </div>
  65. </div>
  66. );
  67. };
  68. const renderSelectedItem = (item) => {
  69. const channelId = item.key || item.value;
  70. const currentEndpoint = channelEndpoints[channelId];
  71. const baseUrl = item._originalData?.base_url || '';
  72. return (
  73. <div key={item.key} style={{ padding: 6 }}>
  74. <div className="flex flex-col gap-2 w-full">
  75. <div className="flex items-center w-full">
  76. <span className="font-medium">{item.label}</span>
  77. <IconClose style={{ cursor: 'pointer' }} onClick={item.onRemove} className="ml-auto" />
  78. </div>
  79. <div className="flex items-center gap-1 ml-4">
  80. <span
  81. className="text-xs text-gray-500 truncate max-w-[120px]"
  82. title={baseUrl}
  83. >
  84. {baseUrl}
  85. </span>
  86. <span className="text-xs text-gray-700 font-mono bg-gray-100 px-2 py-1 rounded flex-1">
  87. {currentEndpoint}
  88. </span>
  89. </div>
  90. </div>
  91. </div>
  92. );
  93. };
  94. const channelFilter = (input, item) => item.label.toLowerCase().includes(input.toLowerCase());
  95. return (
  96. <Modal
  97. visible={visible}
  98. onCancel={onCancel}
  99. onOk={onOk}
  100. title={<span className="text-lg font-semibold">{t('选择同步渠道')}</span>}
  101. width={1000}
  102. >
  103. <Space vertical style={{ width: '100%' }}>
  104. <Card title={t('添加自定义渠道')} className="w-full">
  105. <Space direction="horizontal" style={{ width: '100%' }}>
  106. <Input
  107. placeholder={t('渠道地址,如:https://example.com')}
  108. value={customUrl}
  109. onChange={setCustomUrl}
  110. style={{ flex: 1 }}
  111. />
  112. <Input
  113. placeholder={t('接口路径')}
  114. value={customEndpoint}
  115. onChange={setCustomEndpoint}
  116. style={{ width: 150 }}
  117. />
  118. <Button
  119. icon={<IconPlus />}
  120. onClick={addCustomChannel}
  121. loading={customChannelTesting}
  122. disabled={!customUrl}
  123. className="whitespace-nowrap"
  124. >
  125. {customChannelTesting ? t('测试中...') : t('添加')}
  126. </Button>
  127. </Space>
  128. </Card>
  129. <Transfer
  130. style={{ width: '100%' }}
  131. dataSource={allChannels}
  132. value={selectedChannelIds}
  133. onChange={setSelectedChannelIds}
  134. renderSourceItem={renderSourceItem}
  135. renderSelectedItem={renderSelectedItem}
  136. filter={channelFilter}
  137. inputProps={{ placeholder: t('搜索渠道名称或地址') }}
  138. emptyContent={{
  139. left: t('暂无渠道'),
  140. right: t('暂无选择'),
  141. search: t('无搜索结果'),
  142. }}
  143. />
  144. </Space>
  145. </Modal>
  146. );
  147. }