refactor: KeyList 移除列表模式, 只保留树状模式
- 移除 viewMode、虚拟滚动 (useVirtualizer)、scrollRef - 移除列表视图模板和样式 (.key-flat-list, .key-item 等) - 移除工具栏的列表/树状切换按钮 - 树状叶子节点新增多选 checkbox (原先只有列表模式有) - 保留 .key-checkbox 样式供树状多选使用
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user