删除 Entry 后测试状态显示错位
背景
在 ServiceProvidersPage.tsx 中,点击"全部检测"后可以看到所有 API Key 的测试结果状态(成功/失败)。此时如果删除中间某条 entry,删除后剩余 entry 的测试状态显示不正确。
问题复现
- 有 6 条 API Key Entry,索引 0-5
- 点击"全部检测",所有 entry 都有测试状态
- 删除索引 4(第 5 条,假设是失败状态)
- 原来的索引 5 变成新的索引 4
- Bug:新的索引 4 显示的是旧的索引 4 的状态(error),而不是原来索引 5 的状态(success)
问题原因
keyOf 函数使用 provider.name + entryIdx(数组索引)作为 testStatuses 的 key:
typescript
const keyOf = (provider: OpenAIProviderConfig, entryIdx: number): string =>
`${provider.name}__${entryIdx}`;删除 entry 后,数组索引发生变化,但 testStatuses 中的 key 没有同步更新:
删除前:
testStatuses = {
"provider__0": "success",
"provider__1": "success",
"provider__2": "success",
"provider__3": "success",
"provider__4": "error", ← 这条被删除
"provider__5": "success", ← 这条变成索引 4
}
删除后(数组变化):
apiKeyEntries = [entry0, entry1, entry2, entry3, entry5]
新索引 4 对应原来的 entry5
渲染时:
新索引 4 查 testStatuses["provider__4"] → 得到 "error"(旧值)
但 entry5 的实际状态应该是 "success"根本原因:testStatuses 用数组索引作为 key,删除后索引变化,但状态没有同步更新。
修复方案
方案 A:删除时清理对应状态(不推荐)
在 handleDelete 和 handleBatchDelete 中,删除 entry 后同时清理 testStatuses 和 testErrors 中对应的 key。
问题:这只清理了被删除的那条,后面的索引仍然会错位。
方案 B:用 apiKey 作为 key(推荐)
将 keyOf 改为使用 entry.apiKey 而不是数组索引:
typescript
const keyOf = (provider: OpenAIProviderConfig, entry: ApiKeyEntry): string =>
`${provider.name}__${entry.apiKey}`;这样无论 entry 在数组中的位置如何变化,key 都是稳定的。
实现细节
1. 修改 keyOf 函数签名
typescript
// 修改前
const keyOf = (provider: OpenAIProviderConfig, entryIdx: number): string =>
`${provider.name}__${entryIdx}`;
// 修改后
const keyOf = (provider: OpenAIProviderConfig, entry: ApiKeyEntry): string =>
`${provider.name}__${entry.apiKey}`;2. 修改所有调用 keyOf 的地方
| 位置 | 修改前 | 修改后 |
|---|---|---|
| 第 322 行(filter 逻辑) | keyOf(selectedProvider, indexOf(e)) | keyOf(selectedProvider, e) |
| 第 653 行(testEntry) | keyOf(provider, entryIdx) | keyOf(provider, entry) |
| 第 1026 行(渲染) | keyOf(selectedProvider, realIdx) | keyOf(selectedProvider, entry) |
3. 修改 testEntry 函数
typescript
// 修改前
const testEntry = useCallback(
async (provider: OpenAIProviderConfig, entry: ApiKeyEntry, entryIdx: number) => {
const k = keyOf(provider, entryIdx);
// ...
}
);
// 修改后
const testEntry = useCallback(
async (provider: OpenAIProviderConfig, entry: ApiKeyEntry) => {
const k = keyOf(provider, entry);
// ...
}
);4. 修改调用 testEntry 的地方
typescript
// 修改前
onClick={() => testEntry(selectedProvider, entry, realIdx)}
// 修改后
onClick={() => testEntry(selectedProvider, entry)}涉及文件
| 文件 | 改动 |
|---|---|
ServiceProvidersPage.tsx | 修改 keyOf 函数和所有调用处 |
注意事项
apiKey作为 key 的前提是同一个 provider 下apiKey不重复(当前业务逻辑已保证)- 如果
apiKey为空字符串,key 会是providerName__,多个空 key 会冲突,但当前业务中空 apiKey 的 entry 不会被测试(testEntry中有校验)