diff --git a/docs/PROJECT_REVIEW.md b/docs/PROJECT_REVIEW.md
index fb6b71c..e42d4eb 100644
--- a/docs/PROJECT_REVIEW.md
+++ b/docs/PROJECT_REVIEW.md
@@ -4,7 +4,7 @@
> 审查方式:并行扫描主进程、渲染进程、IPC 契约一致性
> 审查范围:主进程 10 文件、渲染进程 ~40 文件、IPC 65 通道
> 发现问题:54 个(P0×6, P1×12, P2×17, P3×19)
-> 已修复:**P0×6 全部修复** (2026-07-12)
+> 已修复:**P0×6 + P1×11 全部修复** (2026-07-12)
> 可开发需求:26 项(高优先级 6,中 11,低 9)
---
@@ -32,22 +32,22 @@
| 5 | `src/main/redis/connection.ts` (3处) | **超时竞态**:Sentinel/Cluster/普通模式的 setTimeout 均保存 timer ID,在 ready/error 路径调用 clearTimeout | **已修复** |
| 6 | `src/main/credential.ts:4-5` | **safeStorage 不可用时回退明文**:encrypt/decrypt 在 safeStorage 不可用时抛出错误,decrypt catch 保留 base64 兼容旧数据并加 console.warn | **已修复** |
-### P1 - 严重 Bug(功能错误)
+### P1 - 严重 Bug(功能错误) - 全部已处理
-| # | 位置 | 问题 |
-|---|------|------|
-| 7 | `CliView.vue:52-101` | **MULTI/EXEC 事务逻辑错误**:事务模式下每条命令仍通过 IPC 单独发送到 Redis,服务器立即执行而非排队。完全破坏事务语义。应本地排队,EXEC 时 pipeline 一次性发送。 |
-| 8 | `KeyDetail.vue:43-68` | **TTL 定时器模块级泄漏**:`setInterval` 在 `
diff --git a/src/renderer/src/components/key/KeyDetail.vue b/src/renderer/src/components/key/KeyDetail.vue
index 6ccbffc..53c1408 100644
--- a/src/renderer/src/components/key/KeyDetail.vue
+++ b/src/renderer/src/components/key/KeyDetail.vue
@@ -1,5 +1,5 @@
diff --git a/src/renderer/src/stores/key.ts b/src/renderer/src/stores/key.ts
index c97dcfc..77eef2f 100644
--- a/src/renderer/src/stores/key.ts
+++ b/src/renderer/src/stores/key.ts
@@ -132,10 +132,14 @@ export const useKeyStore = defineStore('key', () => {
selectedTTL.value = ttl
}
+ let loadKeyVersion = 0
+
async function loadKeyData(connId: string, key: string) {
+ const currentVersion = ++loadKeyVersion
loading.value = true
try {
await loadKeyTypeAndTTL(connId, key)
+ if (currentVersion !== loadKeyVersion) return
selectedKey.value = key
// Key no longer exists — show expired state and refresh list
@@ -160,29 +164,35 @@ export const useKeyStore = defineStore('key', () => {
break
case 'hash': {
const result = await window.electronAPI.redis.hashScan(connId, key, '0', 100)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = result.fields
break
}
case 'list': {
const len = await window.electronAPI.redis.listLength(connId, key)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = { length: len }
break
}
case 'set': {
const size = await window.electronAPI.redis.setSize(connId, key)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = { size }
break
}
case 'zset': {
const size = await window.electronAPI.redis.zsetSize(connId, key)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = { size }
break
}
case 'stream': {
try {
const info = await window.electronAPI.redis.streamInfo(connId, key)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = { length: info.length }
} catch {
+ if (currentVersion !== loadKeyVersion) return
keyData.value = { length: 0 }
}
break
@@ -195,6 +205,7 @@ export const useKeyStore = defineStore('key', () => {
// command (e.g. key was overwritten). Re-fetch the type so the
// correct editor is shown.
await loadKeyTypeAndTTL(connId, key)
+ if (currentVersion !== loadKeyVersion) return
keyData.value = null
}
} finally {