A:添加动态选择动效

This commit is contained in:
jimmy committed 2025-06-27 16:13:47 +08:00
1 parent 2b182b6d79
commit 5c2b7d03f7
2 files changed
+114 -36

No files matched your search

@@ -23,15 +23,19 @@
ref="appListRef" ref="appListRef"
tabindex="0" tabindex="0"
@keydown="handleKeyDown" @keydown="handleKeyDown"
@blur="handleListBlur"
> >
<div <div
v-for="(app, index) in appInfos" v-for="(app, index) in appInfos"
:key="app.Id" :key="app.Id"
:ref="el => setAppItemRef(el, index)" :ref="el => setAppItemRef(el, index)"
class="app-item" class="app-item"
:class="{ 'app-item--selected': selectedApp?.Id === app.Id }" :class="{
'app-item--selected': selectedApp?.Id === app.Id,
'app-item--focused': focusedIndex === index
}"
@click="selectApp(app)" @click="selectApp(app)"
@mouseenter="handleMouseEnter" @mouseenter="handleMouseEnter(index)"
@mouseleave="handleMouseLeave" @mouseleave="handleMouseLeave"
> >
<!-- 书角折叠效果 --> <!-- 书角折叠效果 -->
@@ -111,6 +115,7 @@ const { appInfos, selectedApp } = storeToRefs(applicationStore)
// Refs // Refs
const appListRef = ref<HTMLDivElement>() const appListRef = ref<HTMLDivElement>()
const appItemRefs = ref<(HTMLDivElement | null)[]>([]) const appItemRefs = ref<(HTMLDivElement | null)[]>([])
const focusedIndex = ref<number>(-1) // 添加聚焦索引
// 设置应用项目的 ref // 设置应用项目的 ref
const setAppItemRef = (el: unknown, index: number) => { const setAppItemRef = (el: unknown, index: number) => {
@@ -149,6 +154,9 @@ onMounted(() => {
watch( watch(
appInfos, appInfos,
async (newAppInfos) => { async (newAppInfos) => {
// 重置聚焦索引
// focusedIndex.value = -1
// 如果有应用且当前没有选中的应用 // 如果有应用且当前没有选中的应用
if (newAppInfos.length > 0 && !selectedApp.value) { if (newAppInfos.length > 0 && !selectedApp.value) {
await nextTick() await nextTick()
@@ -194,56 +202,75 @@ onUnmounted(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if (appInfos.value.length === 0) return if (appInfos.value.length === 0) return
const currentIndex = selectedApp.value let newFocusedIndex = focusedIndex.value
? appInfos.value.findIndex(app => app.Id === selectedApp.value?.Id)
: -1
let newIndex = currentIndex
switch (event.key) { switch (event.key) {
case 'ArrowUp': case 'ArrowUp':
event.preventDefault() event.preventDefault()
// 如果当前没有选中或者是第一个,则选中最后一个 // 如果当前没有聚焦,从选中项开始
if (currentIndex <= 0) { if (focusedIndex.value === -1 && selectedApp.value) {
newIndex = appInfos.value.length - 1 const selectedIndex = appInfos.value.findIndex(app => app.Id === selectedApp.value?.Id)
newFocusedIndex = selectedIndex > 0 ? selectedIndex - 1 : appInfos.value.length - 1
}
// 如果没有聚焦也没有选中,从最后一个开始
else if (focusedIndex.value === -1) {
newFocusedIndex = appInfos.value.length - 1
}
// 正常向上移动
else if (focusedIndex.value === 0) {
newFocusedIndex = appInfos.value.length - 1
} else { } else {
newIndex = currentIndex - 1 newFocusedIndex = focusedIndex.value - 1
} }
break break
case 'ArrowDown': case 'ArrowDown':
event.preventDefault() event.preventDefault()
// 如果当前没有选中或者是最后一个,则选中第一个 // 如果当前没有聚焦,从选中项开始
if (currentIndex === -1 || currentIndex >= appInfos.value.length - 1) { if (focusedIndex.value === -1 && selectedApp.value) {
newIndex = 0 const selectedIndex = appInfos.value.findIndex(app => app.Id === selectedApp.value?.Id)
newFocusedIndex = selectedIndex < appInfos.value.length - 1 ? selectedIndex + 1 : 0
}
// 如果没有聚焦也没有选中,从第一个开始
else if (focusedIndex.value === -1) {
newFocusedIndex = 0
}
// 正常向下移动
else if (focusedIndex.value >= appInfos.value.length - 1) {
newFocusedIndex = 0
} else { } else {
newIndex = currentIndex + 1 newFocusedIndex = focusedIndex.value + 1
} }
break break
case 'Enter': case 'Enter':
event.preventDefault() event.preventDefault()
// 如果有选中的应用,可以在这里触发某些操作 // 如果有聚焦的应用,选中它
if (selectedApp.value) { if (focusedIndex.value >= 0 && focusedIndex.value < appInfos.value.length) {
console.log('Enter pressed on:', selectedApp.value.ProductName) selectApp(appInfos.value[focusedIndex.value])
// 可以触发一个事件或执行某个操作
} }
break break
case 'Escape':
event.preventDefault()
// ESC 键清除聚焦
focusedIndex.value = -1
break
default: default:
return return
} }
// 选择新的应用 // 更新聚焦索引
if (newIndex !== currentIndex && newIndex >= 0 && newIndex < appInfos.value.length) { if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {
selectApp(appInfos.value[newIndex]) focusedIndex.value = newFocusedIndex
// 滚动到可见区域 // 滚动到可见区域
scrollToSelectedItem(newIndex) scrollToFocusedItem(newFocusedIndex)
} }
} }
// 滚动到选中的项目 // 滚动到聚焦的项目
const scrollToSelectedItem = (index: number) => { const scrollToFocusedItem = (index: number) => {
nextTick(() => { nextTick(() => {
const itemEl = appItemRefs.value[index] const itemEl = appItemRefs.value[index]
if (itemEl && appListRef.value) { if (itemEl && appListRef.value) {
@@ -261,6 +288,14 @@ const scrollToSelectedItem = (index: number) => {
}) })
} }
// 列表失去焦点时的处理
const handleListBlur = () => {
// 延迟清除聚焦,避免点击列表项时立即清除
// setTimeout(() => {
// focusedIndex.value = -1
// }, 200)
}
// 格式化内存显示 // 格式化内存显示
const formatMemory = (memoryInBytes: number): string => { const formatMemory = (memoryInBytes: number): string => {
const result = convertFileSize(memoryInBytes, FILE_SIZE_UNIT_ENUM.B) const result = convertFileSize(memoryInBytes, FILE_SIZE_UNIT_ENUM.B)
@@ -278,15 +313,26 @@ const getFirstChar = (name: string): string => {
// 选择应用 // 选择应用
const selectApp = (app: ApplicationType) => { const selectApp = (app: ApplicationType) => {
applicationStore.setSelectedApp(app) applicationStore.setSelectedApp(app)
// 选中后,如果是通过键盘选中的,保持聚焦在当前项
// 如果是通过鼠标点击的,会在 mouseLeave 时自动清除聚焦
const selectedIndex = appInfos.value.findIndex(a => a.Id === app.Id)
if (focusedIndex.value === selectedIndex) {
// 通过键盘选中的,保持聚焦
} else {
// 通过鼠标选中的,清除聚焦
focusedIndex.value = -1
}
} }
// 鼠标悬停事件(预留扩展) // 鼠标悬停事件
const handleMouseEnter = () => { const handleMouseEnter = (index: number) => {
// 可以在这里添加额外的悬停逻辑 // 鼠标悬停时更新聚焦索引
focusedIndex.value = index
} }
const handleMouseLeave = () => { const handleMouseLeave = () => {
// 可以在这里添加额外的离开逻辑 // 鼠标离开时清除聚焦
focusedIndex.value = -1
} }
</script> </script>
@@ -369,12 +415,6 @@ const handleMouseLeave = () => {
outline: none; /* 移除聚焦时的默认轮廓 */ outline: none; /* 移除聚焦时的默认轮廓 */
} }
/* 聚焦时的样式 */
.app-item:focus {
box-shadow: inset 0 0 0 1px var(--accent-primary);
border-radius: 8px;
}
/* 自定义滚动条样式 */ /* 自定义滚动条样式 */
.app-list::-webkit-scrollbar { .app-list::-webkit-scrollbar {
width: 6px; width: 6px;
@@ -435,6 +475,27 @@ const handleMouseLeave = () => {
z-index: 5; z-index: 5;
} }
/* 键盘导航聚焦状态 */
.app-item--focused {
border-color: var(--accent-primary);
background: var(--bg-hover, var(--bg-card));
box-shadow:
inset 0 0 0 2px var(--accent-primary),
0 4px 12px rgba(59, 130, 246, 0.2);
transform: translateY(-1px);
animation: focusPulse 1.5s ease-in-out infinite;
}
/* 同时聚焦和选中的状态 */
.app-item--focused.app-item--selected {
border-color: var(--accent-primary);
box-shadow:
inset 0 0 0 2px var(--accent-primary),
0 0 0 4px var(--monitor-accent-primary-alpha),
0 8px 25px -5px rgba(59, 130, 246, 0.3);
animation: none;
}
/* 选中元素的左侧强调边框 */ /* 选中元素的左侧强调边框 */
.app-item--selected::before { .app-item--selected::before {
content: ''; content: '';
@@ -672,6 +733,24 @@ const handleMouseLeave = () => {
} }
} }
@keyframes focusPulse {
0% {
box-shadow:
inset 0 0 0 2px var(--accent-primary),
0 4px 12px rgba(59, 130, 246, 0.2);
}
50% {
box-shadow:
inset 0 0 0 2px var(--accent-primary),
0 4px 16px rgba(59, 130, 246, 0.35);
}
100% {
box-shadow:
inset 0 0 0 2px var(--accent-primary),
0 4px 12px rgba(59, 130, 246, 0.2);
}
}
/* 响应式 */ /* 响应式 */
@media (max-width: 768px) { @media (max-width: 768px) {
.sidebar-content { .sidebar-content {
@@ -29,7 +29,6 @@
<!-- 右侧检查器面板 --> <!-- 右侧检查器面板 -->
<MonitorInspector <MonitorInspector
v-show="false"
:width="inspectorWidth" :width="inspectorWidth"
:selectedConnection="selectedConnection" :selectedConnection="selectedConnection"
/> />