From 67546f4b2adad40ed38f51d177d73daa728fd666 Mon Sep 17 00:00:00 2001 From: "Apple\\Apple" Date: Thu, 19 Jun 2025 16:05:50 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20chore(ui):=20enhance=20channel=20se?= =?UTF-8?q?lector=20with=20status=20avatars=20and=20UI=20improvements?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add visual status indicators and improve user experience for the upstream ratio sync channel selector modal. Features: - Add status-based avatar indicators for channels (enabled/disabled/auto-disabled) - Implement search functionality with text highlighting - Add endpoint configuration input for each channel - Optimize component structure with reusable ChannelInfo component UI Improvements: - Custom styling for transfer component items - Hide scrollbars for cleaner appearance in transfer lists - Responsive layout adjustments for channel information display - Color-coded avatars: green (enabled), red (disabled), amber (auto-disabled), grey (unknown) Code Quality: - Extract channel status configuration to constants - Create reusable ChannelInfo component to reduce code duplication - Implement proper search filtering for both channel names and URLs - Add consistent styling classes for transfer demo components Files modified: - web/src/components/settings/ChannelSelectorModal.js - web/src/pages/Setting/Ratio/UpstreamRatioSync.js - web/src/index.css This enhancement provides better visual feedback for channel status and improves the overall user experience when selecting channels for ratio synchronization. --- controller/ratio_sync.go | 2 +- .../settings/ChannelSelectorModal.js | 125 +++++++++++------- web/src/index.css | 68 ++++++++++ .../pages/Setting/Ratio/UpstreamRatioSync.js | 8 -- 4 files changed, 144 insertions(+), 59 deletions(-) diff --git a/controller/ratio_sync.go b/controller/ratio_sync.go index fae0c59c..490a2a74 100644 --- a/controller/ratio_sync.go +++ b/controller/ratio_sync.go @@ -49,7 +49,7 @@ func FetchUpstreamRatios(c *gin.Context) { req.Timeout = 10 } - // build upstream list from ids + custom + // build upstream list from ids var upstreams []dto.UpstreamDTO if len(req.ChannelIDs) > 0 { // convert []int64 -> []int for model function diff --git a/web/src/components/settings/ChannelSelectorModal.js b/web/src/components/settings/ChannelSelectorModal.js index 35059473..573329b3 100644 --- a/web/src/components/settings/ChannelSelectorModal.js +++ b/web/src/components/settings/ChannelSelectorModal.js @@ -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 ( -
-
-
- - {item.label} - + <> + + {statusConfig.text} + +
+
+ {isSelected ? ( + item.label + ) : ( + + )}
-
- - {baseUrl} +
+ + {isSelected ? ( + baseUrl + ) : ( + + )} - updateChannelEndpoint(channelId, value)} - placeholder="/api/ratio_config" - className="flex-1 text-xs" - style={{ fontSize: '12px' }} - /> + {showEndpoint && ( + updateChannelEndpoint(channelId, value)} + placeholder="/api/ratio_config" + className="flex-1 text-xs" + style={{ fontSize: '12px' }} + /> + )} + {isSelected && !showEndpoint && ( + + {currentEndpoint} + + )}
+ + ); + }; + + const renderSourceItem = (item) => { + return ( +
+ + +
); }; const renderSelectedItem = (item) => { - const channelId = item.key || item.value; - const currentEndpoint = channelEndpoints[channelId]; - const baseUrl = item._originalData?.base_url || ''; - return ( -
-
-
- {item.label} - -
-
- - {baseUrl} - - - {currentEndpoint} - -
-
+
+ +
); }; - 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 ( .semi-table-row { border-bottom: 1px solid rgba(0, 0, 0, 0.1); } +} + +/* ==================== 同步倍率 - 渠道选择器 ==================== */ + +.components-transfer-source-item, +.components-transfer-selected-item { + display: flex; + align-items: center; + padding: 8px; +} + +.semi-transfer-left-list, +.semi-transfer-right-list { + -ms-overflow-style: none; + scrollbar-width: none; +} + +.semi-transfer-left-list::-webkit-scrollbar, +.semi-transfer-right-list::-webkit-scrollbar { + display: none; +} + +.components-transfer-source-item .semi-checkbox, +.components-transfer-selected-item .semi-checkbox { + display: flex; + align-items: center; + width: 100%; +} + +.components-transfer-source-item .semi-avatar, +.components-transfer-selected-item .semi-avatar { + margin-right: 12px; + flex-shrink: 0; +} + +.components-transfer-source-item .info, +.components-transfer-selected-item .info { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; + justify-content: center; +} + +.components-transfer-source-item .name, +.components-transfer-selected-item .name { + font-weight: 500; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.components-transfer-source-item .email, +.components-transfer-selected-item .email { + font-size: 12px; + color: var(--semi-color-text-2); + display: flex; + align-items: center; +} + +.components-transfer-selected-item .semi-icon-close { + margin-left: 8px; + cursor: pointer; + color: var(--semi-color-text-2); +} + +.components-transfer-selected-item .semi-icon-close:hover { + color: var(--semi-color-text-0); } \ No newline at end of file diff --git a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js index ecf5a1b9..2e12fd3b 100644 --- a/web/src/pages/Setting/Ratio/UpstreamRatioSync.js +++ b/web/src/pages/Setting/Ratio/UpstreamRatioSync.js @@ -42,14 +42,6 @@ export default function UpstreamRatioSync(props) { const [currentPage, setCurrentPage] = useState(1); const [pageSize, setPageSize] = useState(10); - // 当前倍率快照 - const currentRatiosSnapshot = useMemo(() => ({ - model_ratio: JSON.parse(props.options.ModelRatio || '{}'), - completion_ratio: JSON.parse(props.options.CompletionRatio || '{}'), - cache_ratio: JSON.parse(props.options.CacheRatio || '{}'), - model_price: JSON.parse(props.options.ModelPrice || '{}'), - }), [props.options]); - // 获取所有渠道 const fetchAllChannels = async () => { setLoading(true);