A:增加默认选中和键盘切换选中
This commit is contained in:
1 parent
892fdf497f
commit
2b182b6d79
3 files changed
+253
-25
No files matched your search
@@ -6,6 +6,109 @@ import { ApplicationType } from '@/types/infoModel'
|
|||||||
|
|
||||||
export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
||||||
const appInfos: Ref<ApplicationType[]> = ref([])
|
const appInfos: Ref<ApplicationType[]> = ref([])
|
||||||
|
// 状态
|
||||||
|
const selectedApp = ref<ApplicationType | null>(null)
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const activeApps = computed(() => {
|
||||||
|
return appInfos.value.filter((app) => !app.ExitCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
const inactiveApps = computed(() => {
|
||||||
|
return appInfos.value.filter((app) => !!app.ExitCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
const selectedAppIndex = computed(() => {
|
||||||
|
if (!selectedApp.value) return -1
|
||||||
|
return appInfos.value.findIndex((app) => app.Id === selectedApp.value?.Id)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalMemoryUsage = computed(() => {
|
||||||
|
return appInfos.value.reduce((total, app) => total + app.UseMemory, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
const totalProcessCount = computed(() => {
|
||||||
|
return appInfos.value.reduce((total, app) => total + app.ProcessIds.length, 0)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 操作
|
||||||
|
const setAppInfos = (apps: ApplicationType[]) => {
|
||||||
|
appInfos.value = apps
|
||||||
|
|
||||||
|
// 如果当前选中的应用不在新列表中,清空选中
|
||||||
|
if (selectedApp.value && !apps.find((app) => app.Id === selectedApp.value?.Id)) {
|
||||||
|
selectedApp.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setSelectedApp = (app: ApplicationType | null) => {
|
||||||
|
selectedApp.value = app
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectNextApp = () => {
|
||||||
|
if (appInfos.value.length === 0) return
|
||||||
|
|
||||||
|
const currentIndex = selectedAppIndex.value
|
||||||
|
let newIndex = 0
|
||||||
|
|
||||||
|
if (currentIndex === -1 || currentIndex >= appInfos.value.length - 1) {
|
||||||
|
newIndex = 0
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedApp.value = appInfos.value[newIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectPreviousApp = () => {
|
||||||
|
if (appInfos.value.length === 0) return
|
||||||
|
|
||||||
|
const currentIndex = selectedAppIndex.value
|
||||||
|
let newIndex = appInfos.value.length - 1
|
||||||
|
|
||||||
|
if (currentIndex <= 0) {
|
||||||
|
newIndex = appInfos.value.length - 1
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedApp.value = appInfos.value[newIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateApp = (updatedApp: ApplicationType) => {
|
||||||
|
const index = appInfos.value.findIndex((app) => app.Id === updatedApp.Id)
|
||||||
|
if (index !== -1) {
|
||||||
|
appInfos.value[index] = updatedApp
|
||||||
|
|
||||||
|
// 如果更新的是当前选中的应用,同步更新选中状态
|
||||||
|
if (selectedApp.value?.Id === updatedApp.Id) {
|
||||||
|
selectedApp.value = updatedApp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeApp = (appId: string) => {
|
||||||
|
const index = appInfos.value.findIndex((app) => app.Id === appId)
|
||||||
|
if (index !== -1) {
|
||||||
|
appInfos.value.splice(index, 1)
|
||||||
|
|
||||||
|
// 如果删除的是当前选中的应用,选择下一个
|
||||||
|
if (selectedApp.value?.Id === appId) {
|
||||||
|
if (appInfos.value.length > 0) {
|
||||||
|
// 优先选择同位置的应用,如果没有则选择前一个
|
||||||
|
const newIndex = Math.min(index, appInfos.value.length - 1)
|
||||||
|
selectedApp.value = appInfos.value[newIndex]
|
||||||
|
} else {
|
||||||
|
selectedApp.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearAll = () => {
|
||||||
|
appInfos.value = []
|
||||||
|
selectedApp.value = null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订阅信息
|
* 订阅信息
|
||||||
@@ -26,5 +129,25 @@ export const useApplicationStore = defineStore('applicationInfoSub', () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { appInfos, subscribe }
|
return {
|
||||||
|
subscribe, // 状态
|
||||||
|
appInfos,
|
||||||
|
selectedApp,
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
activeApps,
|
||||||
|
inactiveApps,
|
||||||
|
selectedAppIndex,
|
||||||
|
totalMemoryUsage,
|
||||||
|
totalProcessCount,
|
||||||
|
|
||||||
|
// 操作
|
||||||
|
setAppInfos,
|
||||||
|
setSelectedApp,
|
||||||
|
selectNextApp,
|
||||||
|
selectPreviousApp,
|
||||||
|
updateApp,
|
||||||
|
removeApp,
|
||||||
|
clearAll,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
@@ -18,10 +18,16 @@
|
|||||||
|
|
||||||
<!-- 应用列表容器 - 添加独立滚动 -->
|
<!-- 应用列表容器 - 添加独立滚动 -->
|
||||||
<div class="app-list-container">
|
<div class="app-list-container">
|
||||||
<div class="app-list scrollbar-primary scrollbar-thin">
|
<div
|
||||||
|
class="app-list scrollbar-primary scrollbar-thin"
|
||||||
|
ref="appListRef"
|
||||||
|
tabindex="0"
|
||||||
|
@keydown="handleKeyDown"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="app in appInfos"
|
v-for="(app, index) in appInfos"
|
||||||
:key="app.Id"
|
:key="app.Id"
|
||||||
|
: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 }"
|
||||||
@click="selectApp(app)"
|
@click="selectApp(app)"
|
||||||
@@ -77,7 +83,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, defineProps, watch, onMounted, onUnmounted } from 'vue'
|
import { ref, defineProps, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { NIcon } from 'naive-ui'
|
import { NIcon } from 'naive-ui'
|
||||||
import { storeToRefs } from 'pinia'
|
import { storeToRefs } from 'pinia'
|
||||||
import { DesktopOutline } from '@vicons/ionicons5'
|
import { DesktopOutline } from '@vicons/ionicons5'
|
||||||
@@ -100,13 +106,22 @@ const props = defineProps<{
|
|||||||
const webSocketStore = useWebSocketStore()
|
const webSocketStore = useWebSocketStore()
|
||||||
const applicationStore = useApplicationStore()
|
const applicationStore = useApplicationStore()
|
||||||
const { isOpen } = storeToRefs(webSocketStore)
|
const { isOpen } = storeToRefs(webSocketStore)
|
||||||
const { appInfos } = storeToRefs(applicationStore)
|
const { appInfos, selectedApp } = storeToRefs(applicationStore)
|
||||||
|
|
||||||
// 选中的应用
|
// Refs
|
||||||
const selectedApp = ref<ApplicationType | null>(null)
|
const appListRef = ref<HTMLDivElement>()
|
||||||
|
const appItemRefs = ref<(HTMLDivElement | null)[]>([])
|
||||||
|
|
||||||
|
// 设置应用项目的 ref
|
||||||
|
const setAppItemRef = (el: unknown, index: number) => {
|
||||||
|
if (el) {
|
||||||
|
appItemRefs.value[index] = el
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 监听 WebSocket 连接状态
|
// 监听 WebSocket 连接状态
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 监听 WebSocket 状态
|
||||||
watch(
|
watch(
|
||||||
isOpen,
|
isOpen,
|
||||||
(newValue) => {
|
(newValue) => {
|
||||||
@@ -129,6 +144,34 @@ onMounted(() => {
|
|||||||
},
|
},
|
||||||
{ immediate: true },
|
{ immediate: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 监听应用列表变化,自动选中第一个
|
||||||
|
watch(
|
||||||
|
appInfos,
|
||||||
|
async (newAppInfos) => {
|
||||||
|
// 如果有应用且当前没有选中的应用
|
||||||
|
if (newAppInfos.length > 0 && !selectedApp.value) {
|
||||||
|
await nextTick()
|
||||||
|
selectApp(newAppInfos[0])
|
||||||
|
}
|
||||||
|
// 如果当前选中的应用不在列表中了,选中第一个
|
||||||
|
else if (newAppInfos.length > 0 && selectedApp.value &&
|
||||||
|
!newAppInfos.find(app => app.Id === selectedApp.value?.Id)) {
|
||||||
|
await nextTick()
|
||||||
|
selectApp(newAppInfos[0])
|
||||||
|
}
|
||||||
|
// 如果没有应用了,清空选中
|
||||||
|
else if (newAppInfos.length === 0) {
|
||||||
|
applicationStore.setSelectedApp(null)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 自动聚焦到列表容器
|
||||||
|
nextTick(() => {
|
||||||
|
appListRef.value?.focus()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
@@ -147,6 +190,77 @@ onUnmounted(() => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 处理键盘事件
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (appInfos.value.length === 0) return
|
||||||
|
|
||||||
|
const currentIndex = selectedApp.value
|
||||||
|
? appInfos.value.findIndex(app => app.Id === selectedApp.value?.Id)
|
||||||
|
: -1
|
||||||
|
|
||||||
|
let newIndex = currentIndex
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowUp':
|
||||||
|
event.preventDefault()
|
||||||
|
// 如果当前没有选中或者是第一个,则选中最后一个
|
||||||
|
if (currentIndex <= 0) {
|
||||||
|
newIndex = appInfos.value.length - 1
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex - 1
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'ArrowDown':
|
||||||
|
event.preventDefault()
|
||||||
|
// 如果当前没有选中或者是最后一个,则选中第一个
|
||||||
|
if (currentIndex === -1 || currentIndex >= appInfos.value.length - 1) {
|
||||||
|
newIndex = 0
|
||||||
|
} else {
|
||||||
|
newIndex = currentIndex + 1
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
event.preventDefault()
|
||||||
|
// 如果有选中的应用,可以在这里触发某些操作
|
||||||
|
if (selectedApp.value) {
|
||||||
|
console.log('Enter pressed on:', selectedApp.value.ProductName)
|
||||||
|
// 可以触发一个事件或执行某个操作
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择新的应用
|
||||||
|
if (newIndex !== currentIndex && newIndex >= 0 && newIndex < appInfos.value.length) {
|
||||||
|
selectApp(appInfos.value[newIndex])
|
||||||
|
// 滚动到可见区域
|
||||||
|
scrollToSelectedItem(newIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 滚动到选中的项目
|
||||||
|
const scrollToSelectedItem = (index: number) => {
|
||||||
|
nextTick(() => {
|
||||||
|
const itemEl = appItemRefs.value[index]
|
||||||
|
if (itemEl && appListRef.value) {
|
||||||
|
const listRect = appListRef.value.getBoundingClientRect()
|
||||||
|
const itemRect = itemEl.getBoundingClientRect()
|
||||||
|
|
||||||
|
// 如果项目不在可见区域内,滚动到中心位置
|
||||||
|
if (itemRect.top < listRect.top || itemRect.bottom > listRect.bottom) {
|
||||||
|
itemEl.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化内存显示
|
// 格式化内存显示
|
||||||
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)
|
||||||
@@ -163,7 +277,7 @@ const getFirstChar = (name: string): string => {
|
|||||||
|
|
||||||
// 选择应用
|
// 选择应用
|
||||||
const selectApp = (app: ApplicationType) => {
|
const selectApp = (app: ApplicationType) => {
|
||||||
selectedApp.value = app
|
applicationStore.setSelectedApp(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 鼠标悬停事件(预留扩展)
|
// 鼠标悬停事件(预留扩展)
|
||||||
@@ -174,21 +288,6 @@ const handleMouseEnter = () => {
|
|||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = () => {
|
||||||
// 可以在这里添加额外的离开逻辑
|
// 可以在这里添加额外的离开逻辑
|
||||||
}
|
}
|
||||||
|
|
||||||
// 暴露方法给父组件
|
|
||||||
defineExpose({
|
|
||||||
clearSelection: () => {
|
|
||||||
selectedApp.value = null
|
|
||||||
},
|
|
||||||
selectById: (id: string) => {
|
|
||||||
const app = appInfos.value.find((a) => a.Id === id)
|
|
||||||
if (app) {
|
|
||||||
selectApp(app)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// 新增获取当前选中应用的方法
|
|
||||||
getSelectedApp: () => selectedApp.value,
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -267,6 +366,13 @@ defineExpose({
|
|||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
padding-right: 8px; /* 为滚动条留出空间 */
|
padding-right: 8px; /* 为滚动条留出空间 */
|
||||||
margin-right: -8px; /* 抵消padding */
|
margin-right: -8px; /* 抵消padding */
|
||||||
|
outline: none; /* 移除聚焦时的默认轮廓 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 聚焦时的样式 */
|
||||||
|
.app-item:focus {
|
||||||
|
box-shadow: inset 0 0 0 1px var(--accent-primary);
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 自定义滚动条样式 */
|
/* 自定义滚动条样式 */
|
||||||
@@ -550,7 +656,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
filter: drop-shadow(1px 1px 3px rgba(0, 0, 0, 0.15))
|
filter: drop-shadow(1px 1px 3px rgba(0, 0, 0, 0.15))
|
||||||
drop-shadow(0 0 8px rgba(59, 130, 246, 0.4));
|
drop-shadow(0 0 8px rgba(59, 130, 246, 0.4));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
<!-- 中间主视图区域 -->
|
<!-- 中间主视图区域 -->
|
||||||
<MonitorMainPanel
|
<MonitorMainPanel
|
||||||
v-show="false"
|
|
||||||
:headerHeight="mainHeaderHeight"
|
:headerHeight="mainHeaderHeight"
|
||||||
:timelineHeight="timelineHeight"
|
:timelineHeight="timelineHeight"
|
||||||
:searchQuery="searchQuery"
|
:searchQuery="searchQuery"
|
||||||
|
|||||||
Reference in new issue
Block a user