cc-switch 供应商管理实战:拖拽排序、一键复制与删除确认的完整机制解析
本篇技术文章围绕 cc-switch(一款面向 Claude Code、Codex、OpenCode、OpenClaw、Grok Build 与 Hermes Agent 的跨平台桌面助手)用户手册中的「排序与复制」章节展开,系统讲解供应商(Provider)的拖拽重排、快速复制与删除三大日常操作。读完本文,你不仅能在界面上熟练完成这三类操作,还能结合 cc-switch 的 React 前端与 Rust(Tauri)后端源码,理解排序索引 sort_index 的持久化机制、复制操作如何生成不冲突的供应商标识,以及删除保护与故障转移队列之间的联动关系。
一、拖拽排序:调整供应商的展示与故障转移顺序
cc-switch 允许通过拖拽调整供应商列表的显示顺序,核心交互依赖卡片左侧的拖拽手柄:
- 将鼠标移到供应商卡片左侧的 ≡ 拖拽手柄上;
- 按住鼠标左键;
- 上下拖拽到目标位置;
- 松开鼠标完成重排。
排序主要有两个实际用途:
- 常用置顶:把高频使用的供应商拖到列表顶部,减少切换时的查找成本;
- 故障转移顺序:排序结果会影响故障转移队列(failover queue)的默认顺序——在启用自动故障转移的路由类应用中,列表靠前的供应商拥有更高的接管优先级。
源码解析:排序如何持久化
前端排序逻辑集中在 useDragSort Hook 中,可以从中确认几个关键实现细节:
1. 列表顺序的三级判定规则。 sortedProviders 的计算逻辑(见 useDragSort.ts)按以下优先级排序:
// 1) 有 sortIndex 的供应商按 sortIndex 升序;
// 2) 只有 sortIndex 的一方排在前面;
// 3) 都没有 sortIndex 时,按 createdAt 创建时间升序;
// 4) 时间戳相同则按名称 localeCompare(随界面语言切换 zh-CN / zh-TW / en-US)。
也就是说,从未手动拖拽过的供应商按创建时间排列,一旦拖拽过一次,sortIndex 即成为唯一顺序依据。
2. 拖拽事件绑定。 Hook 使用 @dnd-kit/core 同时注册了 PointerSensor 与 KeyboardSensor,其中 PointerSensor 配置了 activationConstraint: { distance: 8 }(见 useDragSort.ts)——鼠标按下后位移超过 8 像素才触发拖拽,避免普通点击被误判为拖拽;同时支持键盘方向键排序(sortableKeyboardCoordinates)。
3. 落点处理与联动刷新。 拖拽结束的 handleDragEnd 处理(见 useDragSort.ts)依次执行:
- 用
arrayMove重排数组后,为每一个供应商生成新的sortIndex(即落点位置),通过providersApi.updateSortOrder(updates, appId)一次性批量持久化到后端; - 失效
["providers", appId]查询缓存,触发列表重渲染; - 若当前应用是路由类应用(
isProxyAppId(appId)),额外失效["failoverQueue", appId]缓存——源码注释明确写道 “Routing apps derive failover order from sort_index”,这正对应文档中「排序影响故障转移队列默认顺序」的说法; - 最后调用
providersApi.updateTrayMenu()同步刷新系统托盘菜单(托盘更新失败仅记录日志,不影响排序本身的成功)。
4. 后端与队列读取。 排序结果以 sort_index 字段存入 SQLite。数据库查询层直接使用 ORDER BY COALESCE(sort_index, 999999), created_at ASC, id ASC 读取列表(见 providers.rs),而故障转移队列的读取同样按 sort_index 排序(见 failover.rs),两者共用同一顺序源,保证「所见顺序即故障接管顺序」。
二、复制供应商:基于现有配置快速派生新配置
复制功能用于在已有配置基础上快速派生变体,典型场景包括:
- 基于现有配置创建变体(如不同 API Key、不同接入点);
- 为当前配置做备份;
- 建立独立的测试配置,避免污染正式配置。
自 v3.15.0 起,统一供应商(Universal Provider)也提供复制操作(见 v3.15.0-en.md),可以先复制再调整启用的应用与模型。
操作步骤:
- 鼠标悬停供应商卡片,显隐动作按钮组;
- 点击「复制(Duplicate)」按钮;
- 系统自动创建一份带
copy名称后缀的副本; - 编辑副本,按需修改配置。
复制了哪些内容
复制会创建一份完整拷贝,具体覆盖范围如下:
| 内容 | 是否复制 |
|---|---|
| 名称 | 是(追加 copy 后缀) |
| 配置(settingsConfig) | 完整深拷贝 |
| 备注(Notes) | 是 |
| 网站链接(Website Link) | 是 |
| 图标(Icon / iconColor) | 是 |
| 端点列表(Endpoint List,配置的一部分) | 是 |
| 排序位置 | 插入在原供应商正下方 |
源码解析:副本的生成细节
普通供应商的复制入口是 ProviderActions 中的复制图标按钮(onDuplicate 回调存在时才渲染),实际逻辑在 App.tsx 的 handleDuplicateProvider:
const duplicatedProvider = {
name: `${provider.name} copy`,
settingsConfig: deepClone(provider.settingsConfig), // 深拷贝,避免共享引用
websiteUrl: provider.websiteUrl,
category: provider.category,
sortIndex: provider.sortIndex + 1, // 副本插入原供应商正下方
meta: provider.meta ? deepClone(provider.meta) : undefined,
icon: provider.icon,
iconColor: provider.iconColor,
};
可以看到:
- 配置采用
deepClone深拷贝,后续修改副本不会反向影响原配置; sortIndex取原值加 1,并在持久化前把原供应商之后所有供应商的sortIndex各加 1(批量调用providersApi.updateSortOrder),确保副本严格落在原供应商正下方,与上表「插入在原供应商下方」一致;- 若排序调整失败,函数会直接中止添加流程(
return),保证列表顺序与数据落库的一致性。
针对 OpenCode、OpenClaw、Hermes、Pi 这几类会向目标应用原生配置写入供应商的应用,复制逻辑还额外做了标识去重:generateUniqueProviderCopyKey(见 App.tsx)会基于原供应商 ID 生成 <原id>-copy 形式的 providerKey,并与现有供应商 ID、原生配置中已启用的 live provider ID 做并集去重;若冲突则依次尝试 -copy-2、-copy-3……同时设置 addToLive = false,即副本默认不会立即写入目标应用的原生配置,避免复制动作直接改动正在使用的配置。
统一供应商的复制实现更轻量,位于 UniversalProviderPanel:deepClone 原对象后更换新的 id(crypto.randomUUID())、名称加 copy 后缀、刷新 createdAt,随后依次执行 upsert 落库与 sync 同步到已启用的应用。
复制之后通常要改什么
复制完成后,建议至少检查三处:
- 名称:改成有业务含义的名字,避免多个
copy后缀造成混淆; - API Key:如果副本要指向另一个账户,务必替换密钥;
- 接入端点(Endpoint):如果副本要指向另一家服务,替换 Base URL。
三、删除供应商:确认对话框与删除限制
删除步骤:
- 悬停供应商卡片,显隐动作按钮;
- 点击「删除」按钮;
- 在确认对话框中确认删除。
确认对话框会显示:
- 待删除供应商的名称;
- 「删除不可撤销」的警示。
删除的限制与注意点:
- 当前激活供应商:可以被删除,但推荐先切换到其他供应商再删除,避免当前应用的配置出现空档;
- 统一供应商:删除时会一并移除其已关联到各应用的原生配置。
后端删除入口是 Tauri 命令 delete_provider,它按 app_type 定位应用类型后委托 ProviderService::delete 完成数据库记录与相关状态清理;对于 OpenCode、OpenClaw 这类累加型应用,另有 remove_provider_from_live_config 命令只把供应商从原生配置中摘除而保留数据库记录,两者语义不同,删除操作按应用场景走对应路径。
前端对删除按钮本身也有保护逻辑:ProviderActions 中的 canDelete 判定会禁用只读供应商(例如由 Hermes 管理、需在 Hermes Web UI 中编辑的条目)的删除按钮,并通过 title 提示禁用原因,防止误删托管配置。
四、小结
供应商列表的排序、复制、删除三者共同构成 cc-switch 日常配置管理的基础操作:
- 拖拽排序以
sort_index为持久化依据,同时驱动列表展示、故障转移队列顺序与托盘菜单,一次操作三处生效; - 复制操作对配置做深拷贝并自动完成排序插入与标识去重,副本默认不侵入目标应用的原生配置,适合快速搭建多变体方案;
- 删除操作以确认对话框兜底,并结合应用类型区分「彻底删除」与「从原生配置移除」两种语义。
如需进一步了解供应商的创建与编辑流程,可继续阅读 添加供应商 与 编辑供应商 两篇手册章节;排序与故障转移的联动细节也可结合 proxy-guide 深入理解。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
