diff --git a/ROADMAP.md b/ROADMAP.md index df92e1d..6b790ee 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -21,7 +21,7 @@ - [x] **Monaco 编辑器** — 替换 StringEditor 的 textarea,支持 JSON 折叠/展开/语法高亮 - [x] **Redis 命令自动补全** — CLI 输入时智能提示(基于 commands.js 命令数据库) - [x] **CLI SUBSCRIBE/MONITOR** — 实时消息流 + MONITOR 命令流 + 停止按钮 -- [ ] **CLI MULTI/EXEC** — 事务模式支持 +- [x] **CLI MULTI/EXEC** — 事务模式支持 - [ ] **Stream 消费者组** — XINFO GROUPS / XINFO CONSUMERS 展开表格 ## P2 — 重要功能增强 diff --git a/src/components/CliView.vue b/src/components/CliView.vue index a6e6281..51a6bca 100644 --- a/src/components/CliView.vue +++ b/src/components/CliView.vue @@ -15,6 +15,8 @@ const outputRef = ref() const suggestionIndex = ref(-1) const inputRef = ref() const streamingMode = ref<'none' | 'subscribe' | 'monitor'>('none') +const txMode = ref(false) +const txQueue = ref([]) const showSuggestions = computed(() => { return suggestions.value.length > 0 && !input.value.includes(' ') @@ -48,6 +50,57 @@ async function execute() { const args = parts.slice(1) try { + // Handle MULTI + if (command === 'MULTI' && !txMode.value) { + const result = await window.electronAPI.redis.execute(connStore.activeId, command, args) + if (result === 'OK') { + txMode.value = true + txQueue.value = [] + history.value.push({ cmd, result: t('cli.txStarted') }) + } else { + history.value.push({ cmd, result: String(result), error: true }) + } + return + } + + // Handle EXEC (end transaction) + if (command === 'EXEC' && txMode.value) { + const result = await window.electronAPI.redis.execute(connStore.activeId, command, args) + let formatted = '' + if (Array.isArray(result)) { + formatted = result.map((item: any, i: number) => { + if (item === null) return `${i + 1}) (nil)` + if (typeof item === 'number') return `${i + 1}) (integer) ${item}` + if (item instanceof Error) return `${i + 1}) (error) ${item.message}` + return `${i + 1}) "${item}"` + }).join('\n') + } else { + formatted = String(result) + } + history.value.push({ cmd: `EXEC (${txQueue.value.length} commands)`, result: formatted }) + txMode.value = false + txQueue.value = [] + return + } + + // Handle DISCARD (cancel transaction) + if (command === 'DISCARD' && txMode.value) { + await window.electronAPI.redis.execute(connStore.activeId, command, args) + txMode.value = false + txQueue.value = [] + history.value.push({ cmd, result: t('cli.txDiscarded') }) + return + } + + // In transaction mode: queue command + if (txMode.value && !['MULTI', 'EXEC', 'DISCARD', 'WATCH', 'UNWATCH'].includes(command)) { + const result = await window.electronAPI.redis.execute(connStore.activeId, command, args) + txQueue.value.push(cmd) + const queueInfo = t('cli.txQueue', { n: txQueue.value.length }) + history.value.push({ cmd, result: `${t('cli.txQueued')} — ${queueInfo}` }) + return + } + // Handle SUBSCRIBE / PSUBSCRIBE if (command === 'SUBSCRIBE' || command === 'PSUBSCRIBE') { const channels = args.filter(a => !a.startsWith('"')) @@ -232,13 +285,13 @@ onUnmounted(() => {