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', () => {
|
||||
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 scrollbar-primary scrollbar-thin">
|
||||
<div
|
||||
class="app-list scrollbar-primary scrollbar-thin"
|
||||
ref="appListRef"
|
||||
tabindex="0"
|
||||
@keydown="handleKeyDown"
|
||||
>
|
||||
<div
|
||||
v-for="app in appInfos"
|
||||
v-for="(app, index) in appInfos"
|
||||
:key="app.Id"
|
||||
:ref="el => setAppItemRef(el, index)"
|
||||
class="app-item"
|
||||
:class="{ 'app-item--selected': selectedApp?.Id === app.Id }"
|
||||
@click="selectApp(app)"
|
||||
@@ -77,7 +83,7 @@
|
||||
</template>
|
||||
|
||||
<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 { storeToRefs } from 'pinia'
|
||||
import { DesktopOutline } from '@vicons/ionicons5'
|
||||
@@ -100,13 +106,22 @@ const props = defineProps<{
|
||||
const webSocketStore = useWebSocketStore()
|
||||
const applicationStore = useApplicationStore()
|
||||
const { isOpen } = storeToRefs(webSocketStore)
|
||||
const { appInfos } = storeToRefs(applicationStore)
|
||||
const { appInfos, selectedApp } = storeToRefs(applicationStore)
|
||||
|
||||
// 选中的应用
|
||||
const selectedApp = ref<ApplicationType | null>(null)
|
||||
// Refs
|
||||
const appListRef = ref<HTMLDivElement>()
|
||||
const appItemRefs = ref<(HTMLDivElement | null)[]>([])
|
||||
|
||||
// 设置应用项目的 ref
|
||||
const setAppItemRef = (el: unknown, index: number) => {
|
||||
if (el) {
|
||||
appItemRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
// 监听 WebSocket 连接状态
|
||||
onMounted(() => {
|
||||
// 监听 WebSocket 状态
|
||||
watch(
|
||||
isOpen,
|
||||
(newValue) => {
|
||||
@@ -129,6 +144,34 @@ onMounted(() => {
|
||||
},
|
||||
{ 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(() => {
|
||||
@@ -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 result = convertFileSize(memoryInBytes, FILE_SIZE_UNIT_ENUM.B)
|
||||
@@ -163,7 +277,7 @@ const getFirstChar = (name: string): string => {
|
||||
|
||||
// 选择应用
|
||||
const selectApp = (app: ApplicationType) => {
|
||||
selectedApp.value = app
|
||||
applicationStore.setSelectedApp(app)
|
||||
}
|
||||
|
||||
// 鼠标悬停事件(预留扩展)
|
||||
@@ -174,21 +288,6 @@ const handleMouseEnter = () => {
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
@@ -267,6 +366,13 @@ defineExpose({
|
||||
padding-left: 4px;
|
||||
padding-right: 8px; /* 为滚动条留出空间 */
|
||||
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% {
|
||||
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
|
||||
v-show="false"
|
||||
:headerHeight="mainHeaderHeight"
|
||||
:timelineHeight="timelineHeight"
|
||||
:searchQuery="searchQuery"
|
||||
|
||||
Reference in new issue
Block a user