From 587f06eb9f65c69c1924784a23a5854b398efd2a Mon Sep 17 00:00:00 2001 From: jxxghp Date: Fri, 15 May 2026 23:15:14 +0800 Subject: [PATCH] perf: safely optimize list loading --- src/@core/utils/image.ts | 53 ++++++++++++++++++-- src/views/discover/MediaCardListView.vue | 1 + src/views/discover/PersonCardListView.vue | 1 + src/views/plugin/PluginCardListView.vue | 5 ++ src/views/subscribe/SubscribePopularView.vue | 1 + src/views/subscribe/SubscribeShareView.vue | 1 + src/views/workflow/WorkflowShareView.vue | 1 + 7 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/@core/utils/image.ts b/src/@core/utils/image.ts index 546c0d92..8ecaf7d0 100644 --- a/src/@core/utils/image.ts +++ b/src/@core/utils/image.ts @@ -1,5 +1,15 @@ import ColorThief from 'colorthief' +const DEFAULT_DOMINANT_COLOR = '#28A9E1' +const DOMINANT_COLOR_CACHE_LIMIT = 100 +const colorThief = new ColorThief() +const dominantColorCache = new Map>() + +interface DominantColorOptions { + fallback?: string + quality?: number +} + // 将 RGB 转换为十六进制 function rgbStringToHex(rgbArray: number[]): string { if (rgbArray.length !== 3 || rgbArray.some(isNaN)) throw new Error('Invalid RGB string format') @@ -14,11 +24,46 @@ function rgbStringToHex(rgbArray: number[]): string { return `#${toHex(r)}${toHex(g)}${toHex(b)}` } +function getImageCacheKey(image: HTMLImageElement) { + return image.currentSrc || image.src || '' +} + +function rememberDominantColor(key: string, colorPromise: Promise) { + if (!key) return colorPromise + + if (dominantColorCache.size >= DOMINANT_COLOR_CACHE_LIMIT) { + const firstKey = dominantColorCache.keys().next().value + if (firstKey) dominantColorCache.delete(firstKey) + } + + dominantColorCache.set(key, colorPromise) + return colorPromise +} + // 提取主要颜色 -export async function getDominantColor(image: HTMLImageElement): Promise { - const colorThief = new ColorThief() - const dominantColor = colorThief.getColor(image) - return rgbStringToHex(dominantColor) +export async function getDominantColor( + image: HTMLImageElement | undefined | null, + options: DominantColorOptions = {}, +): Promise { + const fallback = options.fallback ?? DEFAULT_DOMINANT_COLOR + + if (!image) return fallback + + const cacheKey = getImageCacheKey(image) + const cachedColor = cacheKey ? dominantColorCache.get(cacheKey) : undefined + if (cachedColor) return cachedColor + + const colorPromise = Promise.resolve() + .then(() => { + const dominantColor = colorThief.getColor(image, options.quality ?? 20) + return rgbStringToHex(dominantColor) + }) + .catch(error => { + console.warn('Failed to extract dominant color:', error) + return fallback + }) + + return rememberDominantColor(cacheKey, colorPromise) } // 预加载图片 diff --git a/src/views/discover/MediaCardListView.vue b/src/views/discover/MediaCardListView.vue index ae3f7ca8..15f0372b 100644 --- a/src/views/discover/MediaCardListView.vue +++ b/src/views/discover/MediaCardListView.vue @@ -118,6 +118,7 @@ async function fetchData({ done }: { done: any }) { page.value++ // 返回加载成功 done('ok') + await nextTick() } } else { // 加载一次 diff --git a/src/views/discover/PersonCardListView.vue b/src/views/discover/PersonCardListView.vue index 2f29b296..9b9b64ce 100644 --- a/src/views/discover/PersonCardListView.vue +++ b/src/views/discover/PersonCardListView.vue @@ -86,6 +86,7 @@ async function fetchData({ done }: { done: any }) { page.value++ // 返回加载成功 done('ok') + await nextTick() } } } else { diff --git a/src/views/plugin/PluginCardListView.vue b/src/views/plugin/PluginCardListView.vue index 8b699771..4a925f24 100644 --- a/src/views/plugin/PluginCardListView.vue +++ b/src/views/plugin/PluginCardListView.vue @@ -923,6 +923,11 @@ watch([dataList, installedFilter, hasUpdateFilter, enabledFilter], () => { function loadMarketMore({ done }: { done: any }) { // 从 dataList 中获取最前面的 20 个元素 const itemsToMove = sortedUninstalledList.value.splice(0, 20) + if (itemsToMove.length === 0) { + done('empty') + return + } + displayUninstalledList.value.push(...itemsToMove) done('ok') } diff --git a/src/views/subscribe/SubscribePopularView.vue b/src/views/subscribe/SubscribePopularView.vue index 303648b9..19ca6f56 100644 --- a/src/views/subscribe/SubscribePopularView.vue +++ b/src/views/subscribe/SubscribePopularView.vue @@ -170,6 +170,7 @@ async function fetchData({ done }: { done: any }) { page.value++ // 返回加载成功 done('ok') + await nextTick() } } else { // 设置加载中 diff --git a/src/views/subscribe/SubscribeShareView.vue b/src/views/subscribe/SubscribeShareView.vue index 6b33b196..ec931454 100644 --- a/src/views/subscribe/SubscribeShareView.vue +++ b/src/views/subscribe/SubscribeShareView.vue @@ -184,6 +184,7 @@ async function fetchData({ done }: { done: any }) { page.value++ // 返回加载成功 done('ok') + await nextTick() } } else { // 设置加载中 diff --git a/src/views/workflow/WorkflowShareView.vue b/src/views/workflow/WorkflowShareView.vue index c4b6b570..8e960156 100644 --- a/src/views/workflow/WorkflowShareView.vue +++ b/src/views/workflow/WorkflowShareView.vue @@ -110,6 +110,7 @@ async function fetchData({ done }: { done: any }) { page.value++ // 返回加载成功 done('ok') + await nextTick() } } else { // 设置加载中