refactor: KeyList 移除列表模式, 只保留树状模式

- 移除 viewMode、虚拟滚动 (useVirtualizer)、scrollRef
- 移除列表视图模板和样式 (.key-flat-list, .key-item 等)
- 移除工具栏的列表/树状切换按钮
- 树状叶子节点新增多选 checkbox (原先只有列表模式有)
- 保留 .key-checkbox 样式供树状多选使用
This commit is contained in:
2026-07-11 15:30:56 +08:00
parent 5a479d416e
commit 4305773f0d
+10 -149
View File
@@ -1,6 +1,5 @@
<script setup lang="ts">
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
import { useVirtualizer } from '@tanstack/vue-virtual'
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useKeyStore } from '@renderer/stores/key'
import { useConnectionStore } from '@renderer/stores/connection'
import { useI18n } from '@renderer/i18n'
@@ -13,7 +12,6 @@ const { t } = useI18n()
const filterText = ref('')
const patternInput = ref('*')
const viewMode = ref<'list' | 'tree'>('list')
const multiSelect = ref(false)
const selectedKeys = ref<Set<string>>(new Set())
const expandedFolders = ref<Set<string>>(new Set())
@@ -26,7 +24,7 @@ const contextMenu = ref<{ visible: boolean; x: number; y: number; key: string }>
// Shift+Click range selection
const lastClickedIndex = ref<number>(-1)
// Filtered keys (must be declared before useVirtualizer which references it)
// Filtered keys
const filteredKeys = computed(() => {
const keys = keyStore.keys.map(k => k.name)
return filterText.value
@@ -34,19 +32,6 @@ const filteredKeys = computed(() => {
: keys
})
// Virtual scrolling
const scrollRef = ref<HTMLElement>()
const useVirtualization = computed(() => viewMode.value === 'list' && filteredKeys.value.length > 100)
const virtualizer = useVirtualizer(
computed(() => ({
count: filteredKeys.value.length,
getScrollElement: () => scrollRef.value ?? null,
estimateSize: () => 32,
overscan: 5,
enabled: useVirtualization.value,
}))
)
// Search history
const HISTORY_KEY = 'keySearchHistory'
const MAX_HISTORY = 20
@@ -503,10 +488,6 @@ function isKeyMultiSelected(keyName: string): boolean {
return selectedKeys.value.has(keyName)
}
watch(filterText, () => {
// Filter is reactive via computed
})
defineExpose({ deselectAll })
// Listen for refresh-key-list event from CLI write commands
@@ -591,15 +572,6 @@ onUnmounted(() => {
<el-button size="small" @click="refreshKeys" :disabled="!connStore.activeId">
<el-icon><Refresh /></el-icon>
</el-button>
<el-tooltip :content="viewMode === 'list' ? t('key.switchTree') : t('key.switchList')" placement="top">
<el-button
size="small"
:type="viewMode === 'tree' ? 'primary' : 'default'"
@click="viewMode = viewMode === 'list' ? 'tree' : 'list'"
>
<el-icon><component :is="viewMode === 'list' ? 'FolderOpened' : 'List'" /></el-icon>
</el-button>
</el-tooltip>
<el-button
size="small"
:type="multiSelect ? 'danger' : 'default'"
@@ -623,78 +595,8 @@ onUnmounted(() => {
</div>
<div class="key-content">
<!-- List View -->
<div v-if="viewMode === 'list'" ref="scrollRef" class="key-flat-list" :class="{ virtualized: useVirtualization }">
<!-- Virtualized list for large datasets -->
<template v-if="useVirtualization">
<div
:style="{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
}"
>
<div
v-for="vItem in virtualizer.getVirtualItems()"
:key="String(vItem.key)"
:style="{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${vItem.start}px)`,
}"
>
<div
class="key-item"
:class="{
active: isKeySelected(filteredKeys[vItem.index]),
selected: isKeyMultiSelected(filteredKeys[vItem.index]),
}"
@click="selectKey(filteredKeys[vItem.index], $event)"
@contextmenu.prevent="onKeyContextMenu($event, filteredKeys[vItem.index])"
>
<input
v-if="multiSelect"
type="checkbox"
class="key-checkbox"
:checked="isKeyMultiSelected(filteredKeys[vItem.index])"
@click.stop
/>
<el-icon class="key-icon"><Document /></el-icon>
<span class="key-name">{{ formatKeyName(filteredKeys[vItem.index]) }}</span>
</div>
</div>
</div>
</template>
<!-- Non-virtualized list for small datasets -->
<template v-else>
<div
v-for="keyName in filteredKeys"
:key="keyName"
class="key-item"
:class="{
active: isKeySelected(keyName),
selected: isKeyMultiSelected(keyName),
}"
@click="selectKey(keyName, $event)"
@contextmenu.prevent="onKeyContextMenu($event, keyName)"
>
<input
v-if="multiSelect"
type="checkbox"
class="key-checkbox"
:checked="isKeyMultiSelected(keyName)"
@click.stop
/>
<el-icon class="key-icon"><Document /></el-icon>
<span class="key-name">{{ formatKeyName(keyName) }}</span>
</div>
</template>
</div>
<!-- Tree View -->
<div v-else class="key-tree">
<div class="key-tree">
<template v-for="item in flattenedTree" :key="item.node.fullPath">
<!-- Folder -->
<div
@@ -719,6 +621,13 @@ onUnmounted(() => {
@click="selectKey(item.node.fullPath, $event)"
@contextmenu.prevent="onKeyContextMenu($event, item.node.fullPath)"
>
<input
v-if="multiSelect"
type="checkbox"
class="key-checkbox"
:checked="isKeyMultiSelected(item.node.fullPath)"
@click.stop
/>
<el-icon class="tree-icon"><Document /></el-icon>
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
</div>
@@ -855,38 +764,6 @@ onUnmounted(() => {
overflow-y: auto;
}
.key-flat-list.virtualized {
height: 100%;
overflow-y: auto;
}
.key-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
cursor: pointer;
font-size: 12px;
color: var(--text-secondary);
border-bottom: 1px solid var(--border-subtle);
transition: background 0.15s, color 0.15s;
}
.key-item:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.key-item.active {
background: var(--accent-bg);
color: var(--accent-light);
}
.key-item.selected {
background: rgba(245, 108, 108, 0.08);
color: var(--color-red);
}
.key-checkbox {
flex-shrink: 0;
width: 14px;
@@ -894,22 +771,6 @@ onUnmounted(() => {
accent-color: var(--accent-light);
}
.key-icon {
flex-shrink: 0;
color: var(--text-muted);
}
.key-item.active .key-icon {
color: var(--accent-light);
}
.key-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-family: 'Cascadia Code', 'Fira Code', monospace;
}
/* Tree View Styles */
.tree-item {
display: flex;