ChannelSelectorModal.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import React, { useState } from 'react';
  2. import {
  3. Modal,
  4. Transfer,
  5. Input,
  6. Space,
  7. Checkbox,
  8. Avatar,
  9. Highlight,
  10. } from '@douyinfe/semi-ui';
  11. import { IconClose } from '@douyinfe/semi-icons';
  12. const CHANNEL_STATUS_CONFIG = {
  13. 1: { color: 'green', text: '启用' },
  14. 2: { color: 'red', text: '禁用' },
  15. 3: { color: 'amber', text: '自禁' },
  16. default: { color: 'grey', text: '未知' }
  17. };
  18. const getChannelStatusConfig = (status) => {
  19. return CHANNEL_STATUS_CONFIG[status] || CHANNEL_STATUS_CONFIG.default;
  20. };
  21. export default function ChannelSelectorModal({
  22. t,
  23. visible,
  24. onCancel,
  25. onOk,
  26. allChannels = [],
  27. selectedChannelIds = [],
  28. setSelectedChannelIds,
  29. channelEndpoints,
  30. updateChannelEndpoint,
  31. }) {
  32. const [searchText, setSearchText] = useState('');
  33. const ChannelInfo = ({ item, showEndpoint = false, isSelected = false }) => {
  34. const channelId = item.key || item.value;
  35. const currentEndpoint = channelEndpoints[channelId];
  36. const baseUrl = item._originalData?.base_url || '';
  37. const status = item._originalData?.status || 0;
  38. const statusConfig = getChannelStatusConfig(status);
  39. return (
  40. <>
  41. <Avatar color={statusConfig.color} size="small">
  42. {statusConfig.text}
  43. </Avatar>
  44. <div className="info">
  45. <div className="name">
  46. {isSelected ? (
  47. item.label
  48. ) : (
  49. <Highlight sourceString={item.label} searchWords={[searchText]} />
  50. )}
  51. </div>
  52. <div className="email" style={showEndpoint ? { display: 'flex', alignItems: 'center', gap: '4px' } : {}}>
  53. <span className="text-xs text-gray-500 truncate max-w-[200px]" title={baseUrl}>
  54. {isSelected ? (
  55. baseUrl
  56. ) : (
  57. <Highlight sourceString={baseUrl} searchWords={[searchText]} />
  58. )}
  59. </span>
  60. {showEndpoint && (
  61. <Input
  62. size="small"
  63. value={currentEndpoint}
  64. onChange={(value) => updateChannelEndpoint(channelId, value)}
  65. placeholder="/api/ratio_config"
  66. className="flex-1 text-xs"
  67. style={{ fontSize: '12px' }}
  68. />
  69. )}
  70. {isSelected && !showEndpoint && (
  71. <span className="text-xs text-gray-700 font-mono bg-gray-100 px-2 py-1 rounded ml-2">
  72. {currentEndpoint}
  73. </span>
  74. )}
  75. </div>
  76. </div>
  77. </>
  78. );
  79. };
  80. const renderSourceItem = (item) => {
  81. return (
  82. <div className="components-transfer-source-item" key={item.key}>
  83. <Checkbox
  84. onChange={item.onChange}
  85. checked={item.checked}
  86. style={{ height: 52, alignItems: 'center' }}
  87. >
  88. <ChannelInfo item={item} showEndpoint={true} />
  89. </Checkbox>
  90. </div>
  91. );
  92. };
  93. const renderSelectedItem = (item) => {
  94. return (
  95. <div className="components-transfer-selected-item" key={item.key}>
  96. <ChannelInfo item={item} isSelected={true} />
  97. <IconClose style={{ cursor: 'pointer' }} onClick={item.onRemove} />
  98. </div>
  99. );
  100. };
  101. const channelFilter = (input, item) => {
  102. const searchLower = input.toLowerCase();
  103. return item.label.toLowerCase().includes(searchLower) ||
  104. (item._originalData?.base_url || '').toLowerCase().includes(searchLower);
  105. };
  106. return (
  107. <Modal
  108. visible={visible}
  109. onCancel={onCancel}
  110. onOk={onOk}
  111. title={<span className="text-lg font-semibold">{t('选择同步渠道')}</span>}
  112. width={1000}
  113. >
  114. <Space vertical style={{ width: '100%' }}>
  115. <Transfer
  116. style={{ width: '100%' }}
  117. dataSource={allChannels}
  118. value={selectedChannelIds}
  119. onChange={setSelectedChannelIds}
  120. renderSourceItem={renderSourceItem}
  121. renderSelectedItem={renderSelectedItem}
  122. filter={channelFilter}
  123. inputProps={{ placeholder: t('搜索渠道名称或地址') }}
  124. onSearch={setSearchText}
  125. emptyContent={{
  126. left: t('暂无渠道'),
  127. right: t('暂无选择'),
  128. search: t('无搜索结果'),
  129. }}
  130. />
  131. </Space>
  132. </Modal>
  133. );
  134. }