密钥管理页面禁用提供商逻辑
背景
在 ServiceProvidersPage.tsx 中,导航栏(侧边栏)显示所有服务提供商,但缺少对禁用(disabled)提供商的管理功能。
用户需求:
- 在导航栏增加一个切换功能,可以选择显示或隐藏禁用的提供商
- 移动逻辑保持不变,表格的移动功能仍然可以将密钥移动到禁用的提供商
- 在主面板中,为选中的提供商提供启用/禁用的开关控件
现状分析
当前实现
- 服务提供商配置中包含
disabled字段(参考buildEditResource函数) - 在表单提交时支持
disabled字段(参考handleFormSubmit函数) - 侧边栏通过
filteredProviders进行搜索过滤,但不支持按禁用状态过滤 - 移动下拉菜单
MoveDropdown和BatchMoveDropdown包含了所有提供商(禁用的也能移动过去),但没有视觉区分
相关代码位置
| 功能 | 文件位置 |
|---|---|
| 状态定义 | ServiceProvidersPage.tsx |
| 侧边栏过滤 | filteredProviders |
| 移动下拉菜单 | MoveDropdown |
| 表单提交 | handleFormSubmit |
| 编辑面板 | 第 1019-1186 行(panelHeader) |
实现方案
方案概述
- 导航栏切换:在侧边栏顶部添加一个 ToggleSwitch 开关,用于控制是否显示禁用的提供商
- 侧边栏过滤逻辑:修改
filteredProviders根据showDisabledProviders状态过滤提供商 - 移动下拉菜单:修改
allProviders参数传递逻辑,确保移动功能仍然支持禁用提供商(保持现有行为) - 面板启用/禁用开关:在主面板的工具栏区域添加 ToggleSwitch 控件,支持快速启用/禁用当前选中的提供商
详细实现
1. 添加状态变量
在组件顶部添加新的状态变量:
typescript
const [showDisabledProviders, setShowDisabledProviders] = useState(true);2. 修改侧边栏过滤逻辑
typescript
const filteredProviders = useMemo(() => {
let result = providers;
// 根据搜索过滤
if (sidebarSearch.trim()) {
const q = sidebarSearch.toLowerCase();
result = result.filter(
(p) => p.name.toLowerCase().includes(q) || (p.baseUrl ?? '').toLowerCase().includes(q)
);
}
// 根据禁用状态过滤
if (!showDisabledProviders) {
result = result.filter((p) => !p.disabled);
}
return result;
}, [providers, sidebarSearch, showDisabledProviders]);3. 侧边栏切换控件
在侧边栏添加 ToggleSwitch 控件:
tsx
<div className={styles.sidebarToggle}>
<span className={styles.sidebarToggleLabel}>
{t('serviceProviders.sidebar.showDisabled')}
</span>
<ToggleSwitch
checked={showDisabledProviders}
onChange={setShowDisabledProviders}
ariaLabel={t('serviceProviders.sidebar.showDisabled')}
/>
</div>4. 侧边栏条目样式
为禁用的提供商条目添加视觉区分样式:
scss
// ServiceProvidersPage.module.scss
.sidebarItemDisabled {
opacity: 0.5;
background-color: var(--muted);
}并在侧边栏渲染时添加条件样式:
tsx
<button
className={`${styles.sidebarItem}${
p.name === selectedName ? ` ${styles.sidebarItemActive}` : ''
}${p.disabled ? ` ${styles.sidebarItemDisabled}` : ''}`}
// ...
>5. 面板启用/禁用开关
在面板工具栏区域添加启用/禁用开关:
tsx
<div className={styles.panelDisableToggle}>
<span className={styles.panelDisableLabel}>
{selectedProvider.disabled
? t('serviceProviders.panel.disabled')
: t('serviceProviders.panel.enabled')
}
</span>
<ToggleSwitch
checked={!selectedProvider.disabled}
onChange={async (checked) => {
const next: OpenAIProviderConfig = {
...selectedProvider,
disabled: !checked,
};
const all = [...providers];
all[providers.indexOf(selectedProvider)] = next;
await providersApi.saveOpenAIProviders(all);
setProviders(all);
showNotification(
checked
? t('serviceProviders.toast.enabled')
: t('serviceProviders.toast.disabled'),
'success'
);
}}
ariaLabel={t('serviceProviders.panel.toggleDisable')}
/>
</div>6. 移动逻辑保持不变
移动下拉菜单 MoveDropdown 和 BatchMoveDropdown 的 allProviders 仍然传入完整的 providers 数组,不需要修改:
tsx
<MoveDropdown
provider={selectedProvider}
allProviders={providers} // 保持传入所有提供商,包括禁用的
onMove={(target) => handleMove(selectedProvider, realIdx, target)}
disabled={testingAny}
/>国际化文本
需要在 zh-CN.json 中添加以下文本:
json
{
"serviceProviders": {
"sidebar": {
"showDisabled": "显示禁用提供商",
"baseUrl": "服务地址",
"searchPlaceholder": "搜索地址..."
},
"panel": {
"enabled": "已启用",
"disabled": "已禁用",
"toggleDisable": "切换提供商启用状态"
},
"toast": {
"enabled": "提供商已启用",
"disabled": "提供商已禁用"
}
}
}UI 布局建议
┌────────────────────────────────────────────────────────────┐
│ 提供商管理 │
├─────────────────┬──────────────────────────────────────────┤
│ [显示禁用提供商 ☑] │ toolbar area │
│ ┌─────────────┐ │ [搜索] [全部/有效/无效] [启用/禁用开关] │
│ │ 🔍 搜索... │ │ │
│ ├─────────────┤ │ ┌────────────────────────────────────┐ │
│ │ Provider A │ │ │ # | API Key | Proxy | Status | Act │ │
│ │ api.example │ │ ├────────────────────────────────────┤ │
│ └─────────────┘ │ │ 1 | sk-***... | — | ✅ 有效 | 🗘 │ │
│ │ Provider B │ │ │ 2 | sk-***... | — | ❌ 无效 | 🗘 │ │
│ │ api.test.co │ │ └────────────────────────────────────┘ │
│ └─────────────┘ │ │
│ │ Provider C* │ │ │
│ │ (已禁用) │ │ │
│ └─────────────┘ │ │
└─────────────────┴──────────────────────────────────────────┘注意事项
- 移动功能不限制:表格的移动功能仍然可以将密钥移动到禁用的提供商,这是保持现有行为的设计决策
- 视觉区分:禁用的提供商在侧边栏应该有视觉区分(如降低透明度、灰色背景等)
- 自动选中:当用户隐藏禁用的提供商时,如果当前选中的是禁用的提供商,应该自动切换到列表中的第一个非禁用提供商
- API 调用:启用/禁用状态变更需要调用
saveOpenAIProviders保存到后端