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`)
|
||||
- **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
|
||||
|
||||
|
||||
+8
-8
@@ -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(当前已加入)
|
||||
|
||||
@@ -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',
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -61,6 +70,40 @@ const locales = [
|
||||
size="small"
|
||||
/>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -299,5 +299,7 @@ export default {
|
||||
language: 'Language',
|
||||
scanCount: 'Keys per SCAN',
|
||||
fontSize: 'Font Size',
|
||||
zoom: 'Page Zoom',
|
||||
fontFamily: 'Monospace Font',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -299,5 +299,7 @@ export default {
|
||||
language: '语言',
|
||||
scanCount: 'SCAN 每次数量',
|
||||
fontSize: '字体大小',
|
||||
zoom: '页面缩放',
|
||||
fontFamily: '等宽字体',
|
||||
},
|
||||
}
|
||||
|
||||
+19
-1
@@ -9,6 +9,8 @@ export const useAppStore = defineStore('app', () => {
|
||||
const isDark = ref(true)
|
||||
const fontSize = ref<number>(Number(localStorage.getItem('fontSize')) || 13)
|
||||
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) {
|
||||
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 }
|
||||
})
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user