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
This commit is contained in:
@@ -8,7 +8,7 @@ Fork of [AnotherRedisDesktopManager](https://github.com/qishibo/AnotherRedisDesk
|
|||||||
|
|
||||||
- **Shell:** Electron 43 (`electron/main/index.ts`)
|
- **Shell:** Electron 43 (`electron/main/index.ts`)
|
||||||
- **Preload:** `electron/preload/index.ts` (contextBridge, typed API)
|
- **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)
|
- **Shared:** `electron/shared/types.ts` (cross-process types)
|
||||||
- **Build:** electron-vite + Vite 6 + TypeScript 5
|
- **Build:** electron-vite + Vite 6 + TypeScript 5
|
||||||
- **Redis client:** ioredis 5 (main process only)
|
- **Redis client:** ioredis 5 (main process only)
|
||||||
@@ -41,20 +41,21 @@ Electron main (electron/main/)
|
|||||||
│ ├── set.ts # members, add, remove
|
│ ├── set.ts # members, add, remove
|
||||||
│ ├── zset.ts # range, add, remove
|
│ ├── zset.ts # range, add, remove
|
||||||
│ ├── stream.ts # info, range, add, trim
|
│ ├── 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)
|
├── store.ts # electron-store (connections, settings)
|
||||||
├── credential.ts # safeStorage encrypt/decrypt
|
├── credential.ts # safeStorage encrypt/decrypt
|
||||||
├── win-state.ts # window position persistence
|
├── win-state.ts # window position persistence
|
||||||
|
├── updater.ts # electron-updater (check/download/install)
|
||||||
└── ipc-handlers.ts # all IPC channel handlers
|
└── ipc-handlers.ts # all IPC channel handlers
|
||||||
|
|
||||||
Preload (electron/preload/)
|
Preload (electron/preload/)
|
||||||
├── index.ts # contextBridge.exposeInMainWorld
|
├── index.ts # contextBridge.exposeInMainWorld
|
||||||
└── index.d.ts # ElectronAPI type declarations
|
└── index.d.ts # ElectronAPI type declarations
|
||||||
|
|
||||||
Shared (electron/shared/)
|
Renderer (src/)
|
||||||
└── types.ts # Cross-process type definitions
|
|
||||||
|
|
||||||
Renderer (src/renderer/src/)
|
|
||||||
├── main.ts # Vue 3 bootstrap (Pinia, Element Plus)
|
├── main.ts # Vue 3 bootstrap (Pinia, Element Plus)
|
||||||
├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar)
|
├── App.vue # Shell layout (TitleBar + Sidebar + MainArea + StatusBar)
|
||||||
├── stores/
|
├── stores/
|
||||||
@@ -82,14 +83,24 @@ Renderer (src/renderer/src/)
|
|||||||
│ ├── StreamEditor.vue # stream entries
|
│ ├── StreamEditor.vue # stream entries
|
||||||
│ ├── CliView.vue # interactive terminal
|
│ ├── CliView.vue # interactive terminal
|
||||||
│ ├── SlowLogView.vue # slow log table
|
│ ├── 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/
|
├── i18n/
|
||||||
│ ├── index.ts # useI18n composable
|
│ ├── index.ts # useI18n composable
|
||||||
│ └── locales/
|
│ └── locales/
|
||||||
│ ├── en.ts # English
|
│ ├── en.ts # English
|
||||||
│ └── zh-CN.ts # 中文
|
│ └── zh-CN.ts # 中文
|
||||||
└── composables/
|
├── composables/
|
||||||
└── useKeyboard.ts # keyboard shortcut handler
|
│ └── 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
|
## IPC Channels
|
||||||
@@ -120,6 +131,22 @@ Renderer (src/renderer/src/)
|
|||||||
| `redis:dbSize` | invoke | DBSIZE |
|
| `redis:dbSize` | invoke | DBSIZE |
|
||||||
| `redis:slowLogGet` | invoke | SLOWLOG GET |
|
| `redis:slowLogGet` | invoke | SLOWLOG GET |
|
||||||
| `redis:memoryUsage` | invoke | MEMORY USAGE |
|
| `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:getConnections` | invoke | Read connections |
|
||||||
| `storage:saveConnection` | invoke | Save connection |
|
| `storage:saveConnection` | invoke | Save connection |
|
||||||
| `storage:deleteConnection` | invoke | Delete connection |
|
| `storage:deleteConnection` | invoke | Delete connection |
|
||||||
@@ -131,6 +158,9 @@ Renderer (src/renderer/src/)
|
|||||||
| `window:minimize` | send | Minimize window |
|
| `window:minimize` | send | Minimize window |
|
||||||
| `window:maximize` | send | Toggle maximize |
|
| `window:maximize` | send | Toggle maximize |
|
||||||
| `window:close` | send | Close window |
|
| `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
|
## Keyboard Shortcuts
|
||||||
|
|
||||||
@@ -152,8 +182,8 @@ Renderer (src/renderer/src/)
|
|||||||
|
|
||||||
- Root `tsconfig.json` references `tsconfig.node.json` (main + preload) and `tsconfig.web.json` (renderer)
|
- 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`
|
- Node target includes `src/main/**/*`, `src/preload/**/*`, `electron.vite.config.ts`
|
||||||
- Web target includes `src/renderer/**/*`
|
- Web target includes `src/**/*`
|
||||||
- `@renderer` alias resolves to `src/renderer/src`
|
- `@renderer` alias resolves to `src`
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|||||||
+8
-8
@@ -54,8 +54,8 @@
|
|||||||
- [x] **DB 数量自动检测** — CONFIG GET databases + INFO keyspace
|
- [x] **DB 数量自动检测** — CONFIG GET databases + INFO keyspace
|
||||||
- [x] **DB 记忆** — 每个连接记住最后选择的数据库
|
- [x] **DB 记忆** — 每个连接记住最后选择的数据库
|
||||||
- [ ] **更多语言** — de/es/fr/it/ko/pt/ru/tr/ua/vi/tw(当前仅 en/zh-CN)
|
- [ ] **更多语言** — 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
|
- [x] **SlowLog 配置显示** — slowlog-log-slower-than / slowlog-max-len
|
||||||
- [ ] **Cluster SlowLog** — 每个 master 节点分别查询
|
- [ ] **Cluster SlowLog** — 每个 master 节点分别查询
|
||||||
- [ ] **Cluster Keyspace 表** — 集群模式显示各节点 key 数量
|
- [ ] **Cluster Keyspace 表** — 集群模式显示各节点 key 数量
|
||||||
@@ -68,17 +68,17 @@
|
|||||||
- [x] **Zset 排序切换** — ASC/DESC 单选按钮
|
- [x] **Zset 排序切换** — ASC/DESC 单选按钮
|
||||||
- [x] **CLI 自动同步** — CLI 执行 SELECT 后自动同步 UI
|
- [x] **CLI 自动同步** — CLI 执行 SELECT 后自动同步 UI
|
||||||
- [x] **CLI 执行后刷新 key 列表** — set/hset/del 等命令后自动刷新
|
- [x] **CLI 执行后刷新 key 列表** — set/hset/del 等命令后自动刷新
|
||||||
- [ ] **标签页滚轮切换** — 鼠标滚轮切换标签
|
- [x] **标签页滚轮切换** — 鼠标滚轮切换标签
|
||||||
- [ ] **标签页右键菜单** — 关闭/关闭其他/关闭左侧/关闭右侧
|
- [x] **标签页右键菜单** — 关闭/关闭其他/关闭左侧/关闭右侧
|
||||||
- [ ] **Element Plus 深色适配** — 所有组件无白底
|
- [x] **Element Plus 深色适配** — 所有组件无白底
|
||||||
- [ ] **窗口状态持久化** — 大小/位置/最大化状态记忆
|
- [x] **窗口状态持久化** — 大小/位置/最大化状态记忆
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 技术债务
|
## 技术债务
|
||||||
|
|
||||||
- [ ] AGENTS.md 路径更新 — 文档写 `src/renderer/src/components/`,实际是 `src/components/`
|
- [x] AGENTS.md 路径更新 — 文档写 `src/renderer/src/components/`,实际是 `src/components/`
|
||||||
- [ ] `electron/shared/types.ts` 残留字段 — SSH/Sentinel/Cluster/TLS 路径在类型中存在但从未在 UI 中实现
|
- [x] `electron/shared/types.ts` 残留字段 — SSH/Sentinel/Cluster/TLS 路径在类型中存在但从未在 UI 中实现
|
||||||
- [ ] 单元测试 — 当前无任何测试
|
- [ ] 单元测试 — 当前无任何测试
|
||||||
- [ ] E2E 测试 — 无端到端测试
|
- [ ] E2E 测试 — 无端到端测试
|
||||||
- [ ] 构建产物清理 — `electron-dist/` 应加入 .gitignore(当前已加入)
|
- [ ] 构建产物清理 — `electron-dist/` 应加入 .gitignore(当前已加入)
|
||||||
|
|||||||
@@ -17,6 +17,15 @@ const locales = [
|
|||||||
{ value: 'en', label: 'English' },
|
{ value: 'en', label: 'English' },
|
||||||
{ value: 'zh-CN', label: '中文' },
|
{ value: 'zh-CN', label: '中文' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const fontOptions = [
|
||||||
|
'Cascadia Code',
|
||||||
|
'Fira Code',
|
||||||
|
'JetBrains Mono',
|
||||||
|
'Consolas',
|
||||||
|
'Monaco',
|
||||||
|
'Courier New',
|
||||||
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -61,6 +70,40 @@ const locales = [
|
|||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="settings-label">{{ t('settings.zoom') }}</label>
|
||||||
|
<el-slider
|
||||||
|
:model-value="appStore.zoom"
|
||||||
|
@update:model-value="appStore.setZoom($event)"
|
||||||
|
:min="0.5"
|
||||||
|
:max="2.0"
|
||||||
|
:step="0.1"
|
||||||
|
show-input
|
||||||
|
size="small"
|
||||||
|
style="width: 200px"
|
||||||
|
/>
|
||||||
|
<span class="zoom-value">{{ Math.round(appStore.zoom * 100) }}%</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label class="settings-label">{{ t('settings.fontFamily') }}</label>
|
||||||
|
<el-select
|
||||||
|
:model-value="appStore.fontFamily"
|
||||||
|
@change="appStore.setFontFamily($event)"
|
||||||
|
size="small"
|
||||||
|
style="width: 180px"
|
||||||
|
allow-create
|
||||||
|
filterable
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="font in fontOptions"
|
||||||
|
:key="font"
|
||||||
|
:label="font"
|
||||||
|
:value="font"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -89,4 +132,10 @@ const locales = [
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.zoom-value {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
min-width: 40px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -299,5 +299,7 @@ export default {
|
|||||||
language: 'Language',
|
language: 'Language',
|
||||||
scanCount: 'Keys per SCAN',
|
scanCount: 'Keys per SCAN',
|
||||||
fontSize: 'Font Size',
|
fontSize: 'Font Size',
|
||||||
|
zoom: 'Page Zoom',
|
||||||
|
fontFamily: 'Monospace Font',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -299,5 +299,7 @@ export default {
|
|||||||
language: '语言',
|
language: '语言',
|
||||||
scanCount: 'SCAN 每次数量',
|
scanCount: 'SCAN 每次数量',
|
||||||
fontSize: '字体大小',
|
fontSize: '字体大小',
|
||||||
|
zoom: '页面缩放',
|
||||||
|
fontFamily: '等宽字体',
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-1
@@ -9,6 +9,8 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
const isDark = ref(true)
|
const isDark = ref(true)
|
||||||
const fontSize = ref<number>(Number(localStorage.getItem('fontSize')) || 13)
|
const fontSize = ref<number>(Number(localStorage.getItem('fontSize')) || 13)
|
||||||
const scanCount = ref<number>(Number(localStorage.getItem('scanCount')) || 100)
|
const scanCount = ref<number>(Number(localStorage.getItem('scanCount')) || 100)
|
||||||
|
const zoom = ref<number>(Number(localStorage.getItem('zoom')) || 1.0)
|
||||||
|
const fontFamily = ref<string>(localStorage.getItem('fontFamily') || 'Cascadia Code')
|
||||||
|
|
||||||
function applyTheme(mode: ThemeMode, osDark?: boolean) {
|
function applyTheme(mode: ThemeMode, osDark?: boolean) {
|
||||||
theme.value = mode
|
theme.value = mode
|
||||||
@@ -45,5 +47,21 @@ export const useAppStore = defineStore('app', () => {
|
|||||||
localStorage.setItem('scanCount', String(count))
|
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 }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -291,3 +291,68 @@ body {
|
|||||||
border-color: var(--border-color) !important;
|
border-color: var(--border-color) !important;
|
||||||
color: var(--text-primary) !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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user