Feat/virtualizarefactor: virtualization rework — unify Virtual components, fix memory leaks, migrate 15+ consumerstion rework (#472)

This commit is contained in:
Aqr-K
2026-05-15 21:15:30 +08:00
committed by GitHub
parent 0fda7c70de
commit 5953496d84
51 changed files with 2398 additions and 2130 deletions

View File

@@ -10,10 +10,15 @@ import { getPluginTabs } from '@/router/i18n-menu'
import { useDynamicButton } from '@/composables/useDynamicButton'
import { useI18n } from 'vue-i18n'
import PluginMixedSortCard from '@/components/cards/PluginMixedSortCard.vue'
import ProgressiveCardGrid from '@/components/misc/ProgressiveCardGrid.vue'
import VirtualGrid from '@/components/virtual/VirtualGrid.vue'
import VirtualList from '@/components/virtual/VirtualList.vue'
import { useBreakpointCols } from '@/composables/virtual/useBreakpointCols'
import { usePWA } from '@/composables/usePWA'
import { useDynamicHeaderTab } from '@/composables/useDynamicHeaderTab'
// 列数:按视口断点(路由级全宽页,三个 VirtualGrid 共用:已安装/插件文件夹/插件市场)
const cols = useBreakpointCols({ xs: 1, sm: 2, md: 3, lg: 4, xl: 5, xxl: 5 })
// 国际化
const { t } = useI18n()
@@ -298,6 +303,19 @@ const installedScrollToIndex = computed(() => {
return targetIndex >= 0 ? targetIndex : undefined
})
// VirtualGrid 实例 ref用于命令式 scrollToRow桥接 installedScrollToIndex 跳卡片需求)
const installedGridRef = ref<any>(null)
const marketGridRef = ref<any>(null)
watch(installedScrollToIndex, idx => {
if (idx === undefined || !installedGridRef.value) return
const cols = installedGridRef.value.cols?.value ?? 4
const rowIdx = Math.floor(idx / cols)
requestAnimationFrame(() => {
installedGridRef.value?.scrollToRow(rowIdx)
})
})
// 获取文件夹内筛选后的插件
const getFilteredFolderPlugins = (folderName: string) => {
const folderData = pluginFolders.value[folderName]
@@ -919,12 +937,11 @@ watch([dataList, installedFilter, hasUpdateFilter, enabledFilter], () => {
})
})
// 插件市场加载更多数据
function loadMarketMore({ done }: { done: any }) {
// 从 dataList 中获取最前面的 20 个元素
// 插件市场加载更多数据VirtualGrid @load-more 触发)
function loadMarketMore() {
if (sortedUninstalledList.value.length === 0) return
const itemsToMove = sortedUninstalledList.value.splice(0, 20)
displayUninstalledList.value.push(...itemsToMove)
done('ok')
}
// 组件挂载后
@@ -1568,15 +1585,17 @@ function onDragStartPlugin(evt: any) {
/>
</template>
</Draggable>
<ProgressiveCardGrid
<VirtualGrid
v-else-if="shouldVirtualizeInstalledMainList"
ref="installedGridRef"
:items="mixedSortList"
:get-item-key="item => `${item.type}:${item.id}`"
:min-item-width="256"
:estimated-item-height="180"
:scroll-to-index="installedScrollToIndex"
:columns="cols"
:row-estimate-size="260"
:gap="16"
:overscan="3"
use-window-scroll
>
<template #default="{ item }">
<template #item="{ item }">
<PluginMixedSortCard
:item="item"
:plugin-statistics="PluginStatistics"
@@ -1595,7 +1614,7 @@ function onDragStartPlugin(evt: any) {
@drop-to-folder="(event, folderName) => handleDropToFolder(event, folderName)"
/>
</template>
</ProgressiveCardGrid>
</VirtualGrid>
</template>
<template v-else>
@@ -1627,14 +1646,17 @@ function onDragStartPlugin(evt: any) {
/>
</template>
</Draggable>
<ProgressiveCardGrid
<VirtualGrid
v-else-if="shouldVirtualizeInstalledFolderList"
:items="draggableFolderPlugins"
:get-item-key="item => item.id"
:min-item-width="256"
:estimated-item-height="180"
:columns="cols"
:row-estimate-size="260"
:gap="16"
:overscan="3"
key-field="id"
use-window-scroll
>
<template #default="{ item }">
<template #item="{ item }">
<PluginMixedSortCard
:item="{ type: 'plugin', id: item.id, data: item, order: 0 }"
:plugin-statistics="PluginStatistics"
@@ -1650,7 +1672,7 @@ function onDragStartPlugin(evt: any) {
@remove-from-folder="removeFromFolder"
/>
</template>
</ProgressiveCardGrid>
</VirtualGrid>
</template>
</div>
@@ -1671,28 +1693,22 @@ function onDragStartPlugin(evt: any) {
<div>
<LoadingBanner v-if="!isAppMarketLoaded || isMarketRefreshing" class="mt-12" />
<!-- 资源列表 -->
<VInfiniteScroll
v-if="isAppMarketLoaded && !isMarketRefreshing"
mode="intersect"
side="end"
<VirtualGrid
v-if="isAppMarketLoaded && !isMarketRefreshing && displayUninstalledList.length > 0"
ref="marketGridRef"
:items="displayUninstalledList"
@load="loadMarketMore"
:columns="cols"
:row-estimate-size="280"
:gap="16"
:overscan="3"
use-window-scroll
class="overflow-visible"
@load-more="loadMarketMore"
>
<template #loading />
<template #empty />
<ProgressiveCardGrid
v-if="displayUninstalledList.length > 0"
:items="displayUninstalledList"
:get-item-key="item => `${item.id}_v${item.plugin_version}`"
:min-item-width="256"
:estimated-item-height="260"
>
<template #default="{ item }">
<PluginAppCard :plugin="item" :count="PluginStatistics[item.id || '0']" @install="pluginInstalled" />
</template>
</ProgressiveCardGrid>
</VInfiniteScroll>
<template #item="{ item }">
<PluginAppCard :plugin="item" :count="PluginStatistics[item.id || '0']" @install="pluginInstalled" />
</template>
</VirtualGrid>
<NoDataFound
v-if="displayUninstalledList.length === 0 && isAppMarketLoaded"
error-code="404"
@@ -1767,8 +1783,8 @@ function onDragStartPlugin(evt: any) {
</VToolbar>
<VDialogCloseBtn @click="closeSearchDialog" />
<VList v-if="filterPlugins.length > 0" lines="two">
<VVirtualScroll :items="filterPlugins">
<template #default="{ item }">
<VirtualList :items="filterPlugins" :estimate-size="80" :overscan="6" container-height="60vh">
<template #item="{ item }">
<VListItem @click="openPlugin(item)">
<template #prepend>
<VAvatar>
@@ -1800,7 +1816,7 @@ function onDragStartPlugin(evt: any) {
</VListItemSubtitle>
</VListItem>
</template>
</VVirtualScroll>
</VirtualList>
</VList>
</VCard>
</VDialog>