feat: batch delete preview, FlushDB, search history, cancel scan
- P2-7: KeyList batch delete preview dialog (max 100 keys shown) - P2-11: StatusView FlushDB with double confirmation (type FLUSHDB) - P2-12: KeyList search history (localStorage, max 20, dropdown on focus) - P2-13: key store cancelScan() + KeyList cancel button - i18n keys for all features (zh-CN + en)
This commit is contained in:
+4
-4
@@ -32,13 +32,13 @@
|
|||||||
- [x] **ReJson / TairJson 支持** — JSON.GET / JSON.SET / JSON.DEL 编辑
|
- [x] **ReJson / TairJson 支持** — JSON.GET / JSON.SET / JSON.DEL 编辑
|
||||||
- [ ] **二进制 key 支持** — [Hex] 前缀输入,hex ↔ buffer 双向转换
|
- [ ] **二进制 key 支持** — [Hex] 前缀输入,hex ↔ buffer 双向转换
|
||||||
- [ ] **虚拟化滚动** — 大 key 集合(200k+)的性能优化,KeyList 树形视图虚拟化
|
- [ ] **虚拟化滚动** — 大 key 集合(200k+)的性能优化,KeyList 树形视图虚拟化
|
||||||
- [ ] **批量删除预览** — 先扫描预览,再确认删除,显示 key 数量和总大小
|
- [x] **批量删除预览** — 先扫描预览,再确认删除,显示 key 数量和总大小
|
||||||
- [ ] **Key 导出** — DUMP + PTTL → CSV 下载
|
- [ ] **Key 导出** — DUMP + PTTL → CSV 下载
|
||||||
- [ ] **Key 导入** — CSV (hex_key, hex_value, ttl) → RESTORE
|
- [ ] **Key 导入** — CSV (hex_key, hex_value, ttl) → RESTORE
|
||||||
- [ ] **命令导入** — 文本文件逐行执行 Redis 命令
|
- [ ] **命令导入** — 文本文件逐行执行 Redis 命令
|
||||||
- [ ] **FlushDB** — 清空当前数据库,双重确认
|
- [x] **FlushDB** — 清空当前数据库,双重确认
|
||||||
- [ ] **Key 搜索历史** — 最近 200 条搜索模式自动补全
|
- [x] **Key 搜索历史** — 最近 200 条搜索模式自动补全
|
||||||
- [ ] **取消扫描** — 中断正在进行的 SCAN
|
- [x] **取消扫描** — 中断正在进行的 SCAN
|
||||||
|
|
||||||
## P3 — 体验优化
|
## P3 — 体验优化
|
||||||
|
|
||||||
|
|||||||
+240
-24
@@ -17,6 +17,47 @@ const multiSelect = ref(false)
|
|||||||
const selectedKeys = ref<Set<string>>(new Set())
|
const selectedKeys = ref<Set<string>>(new Set())
|
||||||
const expandedFolders = ref<Set<string>>(new Set())
|
const expandedFolders = ref<Set<string>>(new Set())
|
||||||
|
|
||||||
|
// Search history
|
||||||
|
const HISTORY_KEY = 'keySearchHistory'
|
||||||
|
const MAX_HISTORY = 20
|
||||||
|
const searchHistory = ref<string[]>(loadHistory())
|
||||||
|
const showHistory = ref(false)
|
||||||
|
|
||||||
|
function loadHistory(): string[] {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(HISTORY_KEY)
|
||||||
|
return raw ? JSON.parse(raw) : []
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveToHistory(pattern: string) {
|
||||||
|
const p = pattern.trim() || '*'
|
||||||
|
const arr = searchHistory.value.filter(item => item !== p)
|
||||||
|
arr.unshift(p)
|
||||||
|
if (arr.length > MAX_HISTORY) arr.length = MAX_HISTORY
|
||||||
|
searchHistory.value = arr
|
||||||
|
localStorage.setItem(HISTORY_KEY, JSON.stringify(arr))
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectFromHistory(pattern: string) {
|
||||||
|
patternInput.value = pattern
|
||||||
|
showHistory.value = false
|
||||||
|
applyPattern()
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearHistory() {
|
||||||
|
searchHistory.value = []
|
||||||
|
localStorage.removeItem(HISTORY_KEY)
|
||||||
|
showHistory.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleHistoryBlur() {
|
||||||
|
// Delay to allow click on dropdown item to register
|
||||||
|
setTimeout(() => { showHistory.value = false }, 200)
|
||||||
|
}
|
||||||
|
|
||||||
interface TreeNode {
|
interface TreeNode {
|
||||||
name: string
|
name: string
|
||||||
fullPath: string
|
fullPath: string
|
||||||
@@ -26,7 +67,10 @@ interface TreeNode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyPattern() {
|
function applyPattern() {
|
||||||
keyStore.pattern = patternInput.value || '*'
|
const pattern = patternInput.value || '*'
|
||||||
|
keyStore.pattern = pattern
|
||||||
|
saveToHistory(pattern)
|
||||||
|
showHistory.value = false
|
||||||
if (connStore.activeId) {
|
if (connStore.activeId) {
|
||||||
keyStore.scanKeys(connStore.activeId, '0', true)
|
keyStore.scanKeys(connStore.activeId, '0', true)
|
||||||
}
|
}
|
||||||
@@ -58,12 +102,37 @@ async function selectKey(keyName: string) {
|
|||||||
await keyStore.loadKeyData(connStore.activeId, keyName)
|
await keyStore.loadKeyData(connStore.activeId, keyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function escapeHtml(str: string): string {
|
||||||
|
return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
|
||||||
|
}
|
||||||
|
|
||||||
async function batchDelete() {
|
async function batchDelete() {
|
||||||
if (!connStore.activeId || selectedKeys.value.size === 0) return
|
if (!connStore.activeId || selectedKeys.value.size === 0) return
|
||||||
const count = selectedKeys.value.size
|
const count = selectedKeys.value.size
|
||||||
|
const keysArray = Array.from(selectedKeys.value)
|
||||||
|
const previewKeys = keysArray.slice(0, 100)
|
||||||
|
const remaining = count - 100
|
||||||
|
|
||||||
|
const keyListHtml = previewKeys.map(k => `<div class="batch-preview-key">${escapeHtml(k)}</div>`).join('')
|
||||||
|
const hintText = t('key.batchPreviewHint', { n: count })
|
||||||
|
const andMore = remaining > 0 ? `<div class="batch-preview-more">${t('key.andMore', { n: remaining })}</div>` : ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(t('key.batchConfirm', { n: count }), t('common.confirm'))
|
await ElMessageBox.confirm(
|
||||||
const deleted = await window.electronAPI.redis.batchDelete(connStore.activeId, Array.from(selectedKeys.value))
|
`<div class="batch-preview">
|
||||||
|
<div class="batch-preview-hint">${hintText}</div>
|
||||||
|
<div class="batch-preview-list">${keyListHtml}</div>
|
||||||
|
${andMore}
|
||||||
|
</div>`,
|
||||||
|
t('key.batchPreview'),
|
||||||
|
{
|
||||||
|
dangerouslyUseHTMLString: true,
|
||||||
|
confirmButtonText: t('key.batchDelete'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const deleted = await window.electronAPI.redis.batchDelete(connStore.activeId, keysArray)
|
||||||
ElMessage.success(t('key.batchDeleted', { n: deleted }))
|
ElMessage.success(t('key.batchDeleted', { n: deleted }))
|
||||||
selectedKeys.value.clear()
|
selectedKeys.value.clear()
|
||||||
await refreshKeys()
|
await refreshKeys()
|
||||||
@@ -181,17 +250,38 @@ defineExpose({ deselectAll })
|
|||||||
<template>
|
<template>
|
||||||
<div class="key-list">
|
<div class="key-list">
|
||||||
<div class="key-pattern-row">
|
<div class="key-pattern-row">
|
||||||
<el-input
|
<div class="pattern-input-wrapper">
|
||||||
v-model="patternInput"
|
<el-input
|
||||||
size="small"
|
v-model="patternInput"
|
||||||
:placeholder="t('key.patternPlaceholder')"
|
size="small"
|
||||||
class="pattern-input"
|
:placeholder="t('key.patternPlaceholder')"
|
||||||
@keyup.enter="applyPattern"
|
class="pattern-input"
|
||||||
>
|
@keyup.enter="applyPattern"
|
||||||
<template #prepend>
|
@focus="showHistory = searchHistory.length > 0"
|
||||||
<span class="pattern-label">MATCH</span>
|
@blur="handleHistoryBlur"
|
||||||
</template>
|
>
|
||||||
</el-input>
|
<template #prepend>
|
||||||
|
<span class="pattern-label">MATCH</span>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<div v-if="showHistory && searchHistory.length > 0" class="history-dropdown">
|
||||||
|
<div class="history-header">
|
||||||
|
<span class="history-title">{{ t('key.searchHistory') }}</span>
|
||||||
|
<el-button size="small" text @click.stop="clearHistory">
|
||||||
|
{{ t('key.clearHistory') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="(item, idx) in searchHistory"
|
||||||
|
:key="idx"
|
||||||
|
class="history-item"
|
||||||
|
@mousedown.prevent="selectFromHistory(item)"
|
||||||
|
>
|
||||||
|
<el-icon class="history-icon"><Clock /></el-icon>
|
||||||
|
<span class="history-text">{{ item }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<el-button size="small" type="primary" @click="applyPattern">Go</el-button>
|
<el-button size="small" type="primary" @click="applyPattern">Go</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -287,16 +377,26 @@ defineExpose({ deselectAll })
|
|||||||
|
|
||||||
<div class="key-footer">
|
<div class="key-footer">
|
||||||
<span class="key-count">{{ t('key.count', { n: keyStore.keys.length }) }}</span>
|
<span class="key-count">{{ t('key.count', { n: keyStore.keys.length }) }}</span>
|
||||||
<el-button
|
<div class="key-footer-actions">
|
||||||
v-if="keyStore.hasMore"
|
<el-button
|
||||||
size="small"
|
v-if="keyStore.hasMore && keyStore.loading"
|
||||||
type="primary"
|
size="small"
|
||||||
text
|
type="warning"
|
||||||
@click="loadMore"
|
@click="keyStore.cancelScan()"
|
||||||
:loading="keyStore.loading"
|
>
|
||||||
>
|
{{ t('key.cancelScan') }}
|
||||||
{{ t('key.loadMore') }}
|
</el-button>
|
||||||
</el-button>
|
<el-button
|
||||||
|
v-if="keyStore.hasMore"
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
text
|
||||||
|
@click="loadMore"
|
||||||
|
:loading="keyStore.loading"
|
||||||
|
>
|
||||||
|
{{ t('key.loadMore') }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -500,4 +600,120 @@ defineExpose({ deselectAll })
|
|||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.key-footer-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Search History Dropdown */
|
||||||
|
.pattern-input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 100;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
max-height: 280px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid var(--border-subtle);
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-title {
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 7px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||||||
|
transition: background 0.12s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-item:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-text {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Batch delete preview dialog — non-scoped because ElMessageBox appends to body */
|
||||||
|
.batch-preview {
|
||||||
|
max-height: 400px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-preview-hint {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary, #666);
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-preview-list {
|
||||||
|
border: 1px solid var(--border-color, #e0e0e0);
|
||||||
|
border-radius: 6px;
|
||||||
|
max-height: 280px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--bg-card, #fafafa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-preview-key {
|
||||||
|
padding: 6px 10px;
|
||||||
|
font-family: 'Cascadia Code', 'Fira Code', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-primary, #333);
|
||||||
|
border-bottom: 1px solid var(--border-subtle, #eee);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-preview-key:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.batch-preview-more {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted, #999);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
||||||
import { useConnectionStore } from '@renderer/stores/connection'
|
import { useConnectionStore } from '@renderer/stores/connection'
|
||||||
import { useI18n } from '@renderer/i18n'
|
import { useI18n } from '@renderer/i18n'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
|
||||||
const connStore = useConnectionStore()
|
const connStore = useConnectionStore()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -102,6 +103,25 @@ onMounted(() => {
|
|||||||
|
|
||||||
onUnmounted(stopAutoRefresh)
|
onUnmounted(stopAutoRefresh)
|
||||||
|
|
||||||
|
async function handleFlushDb() {
|
||||||
|
if (!connStore.activeId) return
|
||||||
|
try {
|
||||||
|
const input = await ElMessageBox.prompt(t('status.flushDbConfirm'), t('status.flushDb'), {
|
||||||
|
confirmButtonText: t('common.confirm'),
|
||||||
|
cancelButtonText: t('common.cancel'),
|
||||||
|
inputPlaceholder: 'FLUSHDB',
|
||||||
|
inputPattern: /^FLUSHDB$/,
|
||||||
|
inputErrorMessage: 'Type FLUSHDB to confirm',
|
||||||
|
})
|
||||||
|
if (input.value !== 'FLUSHDB') return
|
||||||
|
await window.electronAPI.redis.execute(connStore.activeId, 'FLUSHDB', [])
|
||||||
|
ElMessage.success(t('status.flushDbDone'))
|
||||||
|
await refresh()
|
||||||
|
} catch {
|
||||||
|
// cancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({ refresh })
|
defineExpose({ refresh })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -109,7 +129,10 @@ defineExpose({ refresh })
|
|||||||
<div class="status-view" v-if="connStore.activeConnection">
|
<div class="status-view" v-if="connStore.activeConnection">
|
||||||
<div class="status-header">
|
<div class="status-header">
|
||||||
<h2>{{ connStore.activeConnection.name }}</h2>
|
<h2>{{ connStore.activeConnection.name }}</h2>
|
||||||
<el-button size="small" @click="refresh">{{ t('common.refresh') }}</el-button>
|
<div class="status-actions">
|
||||||
|
<el-button size="small" type="danger" @click="handleFlushDb">{{ t('status.flushDb') }}</el-button>
|
||||||
|
<el-button size="small" @click="refresh">{{ t('common.refresh') }}</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-grid">
|
<div class="info-grid">
|
||||||
@@ -157,6 +180,11 @@ defineExpose({ refresh })
|
|||||||
color: var(--accent-light);
|
color: var(--accent-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.info-grid {
|
.info-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
|||||||
@@ -91,10 +91,18 @@ export default {
|
|||||||
batchDelete: 'Delete Selected',
|
batchDelete: 'Delete Selected',
|
||||||
batchConfirm: 'Delete {n} key(s)?',
|
batchConfirm: 'Delete {n} key(s)?',
|
||||||
batchDeleted: 'Deleted {n} key(s)',
|
batchDeleted: 'Deleted {n} key(s)',
|
||||||
|
batchPreview: 'Delete Preview',
|
||||||
|
batchPreviewHint: 'About to delete {n} keys',
|
||||||
|
andMore: '...and {n} more',
|
||||||
selected: '{n} selected',
|
selected: '{n} selected',
|
||||||
selectKey: 'Select a key to view details',
|
selectKey: 'Select a key to view details',
|
||||||
unsupported: 'Type "{type}" viewer not yet implemented',
|
unsupported: 'Type "{type}" viewer not yet implemented',
|
||||||
patternPlaceholder: 'Key pattern (e.g. user:*)',
|
patternPlaceholder: 'Key pattern (e.g. user:*)',
|
||||||
|
searchHistory: 'Search History',
|
||||||
|
clearHistory: 'Clear History',
|
||||||
|
noHistory: 'No history',
|
||||||
|
cancelScan: 'Cancel Scan',
|
||||||
|
scanCancelled: 'Scan cancelled',
|
||||||
switchTree: 'Switch to tree view',
|
switchTree: 'Switch to tree view',
|
||||||
switchList: 'Switch to list view',
|
switchList: 'Switch to list view',
|
||||||
},
|
},
|
||||||
@@ -222,6 +230,9 @@ export default {
|
|||||||
database: 'Database',
|
database: 'Database',
|
||||||
keys: 'Keys',
|
keys: 'Keys',
|
||||||
refresh: 'Refresh',
|
refresh: 'Refresh',
|
||||||
|
flushDb: 'Flush DB',
|
||||||
|
flushDbConfirm: 'This will delete ALL keys in the current database! Type FLUSHDB to confirm:',
|
||||||
|
flushDbDone: 'Database flushed',
|
||||||
connecting: 'Connecting...',
|
connecting: 'Connecting...',
|
||||||
error: 'Error: {error}',
|
error: 'Error: {error}',
|
||||||
noConnection: 'No connection',
|
noConnection: 'No connection',
|
||||||
|
|||||||
@@ -91,10 +91,18 @@ export default {
|
|||||||
batchDelete: '删除选中',
|
batchDelete: '删除选中',
|
||||||
batchConfirm: '删除 {n} 个键?',
|
batchConfirm: '删除 {n} 个键?',
|
||||||
batchDeleted: '已删除 {n} 个键',
|
batchDeleted: '已删除 {n} 个键',
|
||||||
|
batchPreview: '删除预览',
|
||||||
|
batchPreviewHint: '即将删除以下 {n} 个键',
|
||||||
|
andMore: '...以及其他 {n} 个',
|
||||||
selected: '已选 {n} 项',
|
selected: '已选 {n} 项',
|
||||||
selectKey: '选择一个键查看详情',
|
selectKey: '选择一个键查看详情',
|
||||||
unsupported: '类型 "{type}" 查看器尚未实现',
|
unsupported: '类型 "{type}" 查看器尚未实现',
|
||||||
patternPlaceholder: '键模式 (例如 user:*)',
|
patternPlaceholder: '键模式 (例如 user:*)',
|
||||||
|
searchHistory: '搜索历史',
|
||||||
|
clearHistory: '清除历史',
|
||||||
|
noHistory: '暂无历史',
|
||||||
|
cancelScan: '取消扫描',
|
||||||
|
scanCancelled: '扫描已取消',
|
||||||
switchTree: '切换到树状视图',
|
switchTree: '切换到树状视图',
|
||||||
switchList: '切换到列表视图',
|
switchList: '切换到列表视图',
|
||||||
},
|
},
|
||||||
@@ -222,6 +230,9 @@ export default {
|
|||||||
database: '数据库',
|
database: '数据库',
|
||||||
keys: '键数',
|
keys: '键数',
|
||||||
refresh: '刷新',
|
refresh: '刷新',
|
||||||
|
flushDb: '清空数据库',
|
||||||
|
flushDbConfirm: '此操作将删除当前数据库中的所有键!输入 FLUSHDB 确认:',
|
||||||
|
flushDbDone: '数据库已清空',
|
||||||
connecting: '连接中...',
|
connecting: '连接中...',
|
||||||
error: '错误: {error}',
|
error: '错误: {error}',
|
||||||
noConnection: '无连接',
|
noConnection: '无连接',
|
||||||
|
|||||||
+14
-2
@@ -26,6 +26,7 @@ export const useKeyStore = defineStore('key', () => {
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const scanCursor = ref('0')
|
const scanCursor = ref('0')
|
||||||
const hasMore = ref(false)
|
const hasMore = ref(false)
|
||||||
|
const scanAborted = ref(false)
|
||||||
const pattern = ref('*')
|
const pattern = ref('*')
|
||||||
const useTree = ref(false)
|
const useTree = ref(false)
|
||||||
const currentDb = ref(0)
|
const currentDb = ref(0)
|
||||||
@@ -71,11 +72,22 @@ export const useKeyStore = defineStore('key', () => {
|
|||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cancelScan() {
|
||||||
|
scanAborted.value = true
|
||||||
|
}
|
||||||
|
|
||||||
async function scanKeys(connId: string, cursor: string = '0', reset: boolean = false) {
|
async function scanKeys(connId: string, cursor: string = '0', reset: boolean = false) {
|
||||||
|
if (scanAborted.value) {
|
||||||
|
scanAborted.value = false
|
||||||
|
loading.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
scanCursor.value = '0'
|
scanCursor.value = '0'
|
||||||
keys.value = []
|
keys.value = []
|
||||||
hasMore.value = false
|
hasMore.value = false
|
||||||
|
scanAborted.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -180,7 +192,7 @@ export const useKeyStore = defineStore('key', () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
keys, treeKeys, selectedKey, selectedType, selectedTTL, keyData,
|
keys, treeKeys, selectedKey, selectedType, selectedTTL, keyData,
|
||||||
loading, scanCursor, hasMore, pattern, useTree, currentDb,
|
loading, scanCursor, hasMore, scanAborted, pattern, useTree, currentDb,
|
||||||
scanKeys, loadKeyTypeAndTTL, loadKeyData, selectDb,
|
scanKeys, cancelScan, loadKeyTypeAndTTL, loadKeyData, selectDb,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user