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">
|
<script setup lang="ts">
|
||||||
import { ref, watch, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useVirtualizer } from '@tanstack/vue-virtual'
|
|
||||||
import { useKeyStore } from '@renderer/stores/key'
|
import { useKeyStore } from '@renderer/stores/key'
|
||||||
import { useConnectionStore } from '@renderer/stores/connection'
|
import { useConnectionStore } from '@renderer/stores/connection'
|
||||||
import { useI18n } from '@renderer/i18n'
|
import { useI18n } from '@renderer/i18n'
|
||||||
@@ -13,7 +12,6 @@ const { t } = useI18n()
|
|||||||
|
|
||||||
const filterText = ref('')
|
const filterText = ref('')
|
||||||
const patternInput = ref('*')
|
const patternInput = ref('*')
|
||||||
const viewMode = ref<'list' | 'tree'>('list')
|
|
||||||
const multiSelect = ref(false)
|
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())
|
||||||
@@ -26,7 +24,7 @@ const contextMenu = ref<{ visible: boolean; x: number; y: number; key: string }>
|
|||||||
// Shift+Click range selection
|
// Shift+Click range selection
|
||||||
const lastClickedIndex = ref<number>(-1)
|
const lastClickedIndex = ref<number>(-1)
|
||||||
|
|
||||||
// Filtered keys (must be declared before useVirtualizer which references it)
|
// Filtered keys
|
||||||
const filteredKeys = computed(() => {
|
const filteredKeys = computed(() => {
|
||||||
const keys = keyStore.keys.map(k => k.name)
|
const keys = keyStore.keys.map(k => k.name)
|
||||||
return filterText.value
|
return filterText.value
|
||||||
@@ -34,19 +32,6 @@ const filteredKeys = computed(() => {
|
|||||||
: keys
|
: 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
|
// Search history
|
||||||
const HISTORY_KEY = 'keySearchHistory'
|
const HISTORY_KEY = 'keySearchHistory'
|
||||||
const MAX_HISTORY = 20
|
const MAX_HISTORY = 20
|
||||||
@@ -503,10 +488,6 @@ function isKeyMultiSelected(keyName: string): boolean {
|
|||||||
return selectedKeys.value.has(keyName)
|
return selectedKeys.value.has(keyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(filterText, () => {
|
|
||||||
// Filter is reactive via computed
|
|
||||||
})
|
|
||||||
|
|
||||||
defineExpose({ deselectAll })
|
defineExpose({ deselectAll })
|
||||||
|
|
||||||
// Listen for refresh-key-list event from CLI write commands
|
// 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-button size="small" @click="refreshKeys" :disabled="!connStore.activeId">
|
||||||
<el-icon><Refresh /></el-icon>
|
<el-icon><Refresh /></el-icon>
|
||||||
</el-button>
|
</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
|
<el-button
|
||||||
size="small"
|
size="small"
|
||||||
:type="multiSelect ? 'danger' : 'default'"
|
:type="multiSelect ? 'danger' : 'default'"
|
||||||
@@ -623,78 +595,8 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="key-content">
|
<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 -->
|
<!-- Tree View -->
|
||||||
<div v-else class="key-tree">
|
<div class="key-tree">
|
||||||
<template v-for="item in flattenedTree" :key="item.node.fullPath">
|
<template v-for="item in flattenedTree" :key="item.node.fullPath">
|
||||||
<!-- Folder -->
|
<!-- Folder -->
|
||||||
<div
|
<div
|
||||||
@@ -719,6 +621,13 @@ onUnmounted(() => {
|
|||||||
@click="selectKey(item.node.fullPath, $event)"
|
@click="selectKey(item.node.fullPath, $event)"
|
||||||
@contextmenu.prevent="onKeyContextMenu($event, item.node.fullPath)"
|
@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>
|
<el-icon class="tree-icon"><Document /></el-icon>
|
||||||
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
|
<span class="tree-name">{{ formatKeyName(item.node.name) }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -855,38 +764,6 @@ onUnmounted(() => {
|
|||||||
overflow-y: auto;
|
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 {
|
.key-checkbox {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
@@ -894,22 +771,6 @@ onUnmounted(() => {
|
|||||||
accent-color: var(--accent-light);
|
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 View Styles */
|
||||||
.tree-item {
|
.tree-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
Reference in New Issue
Block a user