A:添加动态选择动效
This commit is contained in:
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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in new issue
Block a user