From 71b2bd17715d76ee753c88ac2c5c9b960da53da4 Mon Sep 17 00:00:00 2001 From: Jokul Date: Tue, 7 Jul 2026 22:53:18 +0800 Subject: [PATCH] feat: P3 final batch - zoom, fonts, dark mode, tech debt cleanup - Page zoom control (0.5-2.0x, localStorage) - Custom monospace font selector (6 presets + custom) - Element Plus dark mode final fixes (dropdown, message-box, switch, popper) - AGENTS.md updated with correct paths + new modules/components/IPC channels - ROADMAP: window state persistence already done via win-state.ts - ROADMAP: tech debt items marked done --- AGENTS.md | 52 ++++++++++++++++++++------ ROADMAP.md | 16 ++++---- src/components/SettingsView.vue | 49 +++++++++++++++++++++++++ src/i18n/locales/en.ts | 2 + src/i18n/locales/zh-CN.ts | 2 + src/stores/app.ts | 20 +++++++++- src/styles/global.css | 65 +++++++++++++++++++++++++++++++++ 7 files changed, 186 insertions(+), 20 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2962a84..7f98126 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,7 +8,7 @@ Fork of [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesk - **Shell:** Electron 43 (`electron/main/index.ts`) - **Preload:** `electron/preload/index.ts` (contextBridge, typed API) -- **Renderer:** Vue 3 + Pinia + Element Plus + Vue Router (`src/renderer/`) +- **Renderer:** Vue 3 + Pinia + Element Plus + Vue Router (`src/`) - **Shared:** `electron/shared/types.ts` (cross-process types) - **Build:** electron-vite + Vite 6 + TypeScript 5 - **Redis client:** ioredis 5 (main process only) @@ -41,20 +41,21 @@ Electron main (electron/main/) │ ├── set.ts # members, add, remove │ ├── zset.ts # range, add, remove │ ├── stream.ts # info, range, add, trim - │ └── server.ts # info, select, ping, slowlog + │ ├── server.ts # info, select, ping, slowlog + │ ├── pubsub.ts # SUBSCRIBE/UNSUBSCRIBE + │ ├── commandLogger.ts # command execution log + │ └── format.ts # value decode/format detection ├── store.ts # electron-store (connections, settings) ├── credential.ts # safeStorage encrypt/decrypt ├── win-state.ts # window position persistence + ├── updater.ts # electron-updater (check/download/install) └── ipc-handlers.ts # all IPC channel handlers Preload (electron/preload/) ├── index.ts # contextBridge.exposeInMainWorld └── index.d.ts # ElectronAPI type declarations -Shared (electron/shared/) - └── types.ts # Cross-process type definitions - -Renderer (src/renderer/src/) +Renderer (src/) ├── main.ts # Vue 3 bootstrap (Pinia, Element Plus) ├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar) ├── stores/ @@ -82,14 +83,24 @@ Renderer (src/renderer/src/) │ ├── StreamEditor.vue # stream entries │ ├── CliView.vue # interactive terminal │ ├── SlowLogView.vue # slow log table - │ └── SettingsView.vue # theme, language, scan count + │ ├── SettingsView.vue # theme, language, scan count + │ ├── MemoryAnalysis.vue # MEMORY USAGE scan & sort + │ ├── CommandLog.vue # command execution history + │ └── ReJsonEditor.vue # JSON.GET / JSON.SET editing ├── i18n/ │ ├── index.ts # useI18n composable │ └── locales/ │ ├── en.ts # English │ └── zh-CN.ts # 中文 - └── composables/ - └── useKeyboard.ts # keyboard shortcut handler + ├── composables/ + │ └── useKeyboard.ts # keyboard shortcut handler + ├── styles/ + │ ├── variables.css # CSS custom properties + │ └── global.css # global overrides + ├── data/ + │ └── commands.ts # Redis command definitions + └── utils/ + └── binary.ts # hex ↔ buffer conversion ``` ## IPC Channels @@ -120,6 +131,22 @@ Renderer (src/renderer/src/) | `redis:dbSize` | invoke | DBSIZE | | `redis:slowLogGet` | invoke | SLOWLOG GET | | `redis:memoryUsage` | invoke | MEMORY USAGE | +| `redis:subscribe` | invoke | SUBSCRIBE to channels | +| `redis:unsubscribe` | invoke | UNSUBSCRIBE from channels | +| `redis:monitor` | invoke | Start MONITOR stream | +| `redis:monitorStop` | invoke | Stop MONITOR stream | +| `redis:getCommandLog` | invoke | Get command execution log | +| `redis:clearCommandLog` | invoke | Clear command log | +| `redis:decodeValue` | invoke | Decode value by format | +| `redis:detectFormat` | invoke | Auto-detect value format | +| `redis:customFormat` | invoke | Custom external formatter | +| `redis:streamGroups` | invoke | XINFO GROUPS | +| `redis:streamConsumers` | invoke | XINFO CONSUMERS | +| `redis:streamGroupCreate` | invoke | XGROUP CREATE | +| `redis:streamGroupDestroy` | invoke | XGROUP DESTROY | +| `redis:streamAck` | invoke | XACK messages | +| `redis:rejsonGet` | invoke | JSON.GET value | +| `redis:rejsonSet` | invoke | JSON.SET value | | `storage:getConnections` | invoke | Read connections | | `storage:saveConnection` | invoke | Save connection | | `storage:deleteConnection` | invoke | Delete connection | @@ -131,6 +158,9 @@ Renderer (src/renderer/src/) | `window:minimize` | send | Minimize window | | `window:maximize` | send | Toggle maximize | | `window:close` | send | Close window | +| `updater:check` | invoke | Check for updates | +| `updater:download` | invoke | Download update | +| `updater:install` | invoke | Quit and install update | ## Keyboard Shortcuts @@ -152,8 +182,8 @@ Renderer (src/renderer/src/) - Root `tsconfig.json` references `tsconfig.node.json` (main + preload) and `tsconfig.web.json` (renderer) - Node target includes `src/main/**/*`, `src/preload/**/*`, `electron.vite.config.ts` -- Web target includes `src/renderer/**/*` -- `@renderer` alias resolves to `src/renderer/src` +- Web target includes `src/**/*` +- `@renderer` alias resolves to `src` ## Security diff --git a/ROADMAP.md b/ROADMAP.md index a363e62..8bf74ba 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -54,8 +54,8 @@ - [x] **DB 数量自动检测** — CONFIG GET databases + INFO keyspace - [x] **DB 记忆** — 每个连接记住最后选择的数据库 - [ ] **更多语言** — de/es/fr/it/ko/pt/ru/tr/ua/vi/tw(当前仅 en/zh-CN) -- [ ] **页面缩放** — 0.5x–2.0x 缩放控制 -- [ ] **自定义字体** — 用户可选等宽字体 +- [x] **页面缩放** — 0.5x–2.0x 缩放控制 +- [x] **自定义字体** — 用户可选等宽字体 - [x] **SlowLog 配置显示** — slowlog-log-slower-than / slowlog-max-len - [ ] **Cluster SlowLog** — 每个 master 节点分别查询 - [ ] **Cluster Keyspace 表** — 集群模式显示各节点 key 数量 @@ -68,17 +68,17 @@ - [x] **Zset 排序切换** — ASC/DESC 单选按钮 - [x] **CLI 自动同步** — CLI 执行 SELECT 后自动同步 UI - [x] **CLI 执行后刷新 key 列表** — set/hset/del 等命令后自动刷新 -- [ ] **标签页滚轮切换** — 鼠标滚轮切换标签 -- [ ] **标签页右键菜单** — 关闭/关闭其他/关闭左侧/关闭右侧 -- [ ] **Element Plus 深色适配** — 所有组件无白底 -- [ ] **窗口状态持久化** — 大小/位置/最大化状态记忆 +- [x] **标签页滚轮切换** — 鼠标滚轮切换标签 +- [x] **标签页右键菜单** — 关闭/关闭其他/关闭左侧/关闭右侧 +- [x] **Element Plus 深色适配** — 所有组件无白底 +- [x] **窗口状态持久化** — 大小/位置/最大化状态记忆 --- ## 技术债务 -- [ ] AGENTS.md 路径更新 — 文档写 `src/renderer/src/components/`,实际是 `src/components/` -- [ ] `electron/shared/types.ts` 残留字段 — SSH/Sentinel/Cluster/TLS 路径在类型中存在但从未在 UI 中实现 +- [x] AGENTS.md 路径更新 — 文档写 `src/renderer/src/components/`,实际是 `src/components/` +- [x] `electron/shared/types.ts` 残留字段 — SSH/Sentinel/Cluster/TLS 路径在类型中存在但从未在 UI 中实现 - [ ] 单元测试 — 当前无任何测试 - [ ] E2E 测试 — 无端到端测试 - [ ] 构建产物清理 — `electron-dist/` 应加入 .gitignore(当前已加入) diff --git a/src/components/SettingsView.vue b/src/components/SettingsView.vue index a2cb8a8..84a8dc1 100644 --- a/src/components/SettingsView.vue +++ b/src/components/SettingsView.vue @@ -17,6 +17,15 @@ const locales = [ { value: 'en', label: 'English' }, { value: 'zh-CN', label: '中文' }, ] + +const fontOptions = [ + 'Cascadia Code', + 'Fira Code', + 'JetBrains Mono', + 'Consolas', + 'Monaco', + 'Courier New', +] @@ -89,4 +132,10 @@ const locales = [ color: var(--text-secondary); min-width: 140px; } + +.zoom-value { + font-size: 12px; + color: var(--text-muted); + min-width: 40px; +} diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts index ca1aa4f..cd4f13a 100644 --- a/src/i18n/locales/en.ts +++ b/src/i18n/locales/en.ts @@ -299,5 +299,7 @@ export default { language: 'Language', scanCount: 'Keys per SCAN', fontSize: 'Font Size', + zoom: 'Page Zoom', + fontFamily: 'Monospace Font', }, } diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 03da5b7..2f11dc3 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -299,5 +299,7 @@ export default { language: '语言', scanCount: 'SCAN 每次数量', fontSize: '字体大小', + zoom: '页面缩放', + fontFamily: '等宽字体', }, } diff --git a/src/stores/app.ts b/src/stores/app.ts index 8416d01..ff44c58 100644 --- a/src/stores/app.ts +++ b/src/stores/app.ts @@ -9,6 +9,8 @@ export const useAppStore = defineStore('app', () => { const isDark = ref(true) const fontSize = ref(Number(localStorage.getItem('fontSize')) || 13) const scanCount = ref(Number(localStorage.getItem('scanCount')) || 100) + const zoom = ref(Number(localStorage.getItem('zoom')) || 1.0) + const fontFamily = ref(localStorage.getItem('fontFamily') || 'Cascadia Code') function applyTheme(mode: ThemeMode, osDark?: boolean) { theme.value = mode @@ -45,5 +47,21 @@ export const useAppStore = defineStore('app', () => { localStorage.setItem('scanCount', String(count)) } - return { theme, isDark, fontSize, scanCount, setTheme, setFontSize, setScanCount } + function setZoom(value: number) { + zoom.value = value + localStorage.setItem('zoom', String(value)) + document.body.style.zoom = String(value) + } + + function setFontFamily(value: string) { + fontFamily.value = value + localStorage.setItem('fontFamily', value) + document.documentElement.style.setProperty('--font-mono', value) + } + + // Apply saved zoom and fontFamily on mount + document.body.style.zoom = String(zoom.value) + document.documentElement.style.setProperty('--font-mono', fontFamily.value) + + return { theme, isDark, fontSize, scanCount, zoom, fontFamily, setTheme, setFontSize, setScanCount, setZoom, setFontFamily } }) diff --git a/src/styles/global.css b/src/styles/global.css index e1814df..b16da40 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -291,3 +291,68 @@ body { border-color: var(--border-color) !important; color: var(--text-primary) !important; } + +/* Dropdown Menu */ +.el-dropdown-menu { + background: var(--bg-secondary) !important; + border-color: var(--border-color) !important; +} + +.el-dropdown-menu__item { + color: var(--text-primary) !important; +} + +.el-dropdown-menu__item:not(.is-disabled):hover { + background: var(--bg-hover) !important; + color: var(--accent-light) !important; +} + +/* Message Box */ +.el-message-box { + background: var(--bg-secondary) !important; + border-color: var(--border-color) !important; +} + +.el-message-box__title { + color: var(--text-primary) !important; +} + +.el-message-box__message { + color: var(--text-primary) !important; +} + +.el-message-box__content { + color: var(--text-primary) !important; +} + +/* Switch */ +.el-switch { + --el-switch-on-color: var(--accent) !important; + --el-switch-off-color: var(--bg-tertiary) !important; +} + +.el-switch__core { + background: var(--bg-tertiary) !important; + border-color: var(--border-color) !important; +} + +.el-switch.is-checked .el-switch__core { + background: var(--accent) !important; + border-color: var(--accent) !important; +} + +/* Loading Mask */ +.el-loading-mask { + background: rgba(0, 0, 0, 0.7) !important; +} + +/* Popper (all popper types) */ +.el-popper { + background: var(--bg-secondary) !important; + border-color: var(--border-color) !important; +} + +.el-popper__arrow::before { + background: var(--bg-secondary) !important; + border-color: var(--border-color) !important; +}