perf: safely optimize list loading

This commit is contained in:
jxxghp
2026-05-15 23:15:14 +08:00
parent 7114c63e8f
commit 587f06eb9f
7 changed files with 59 additions and 4 deletions

View File

@@ -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<string, Promise<string>>()
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<string>) {
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<string> {
const colorThief = new ColorThief()
const dominantColor = colorThief.getColor(image)
return rgbStringToHex(dominantColor)
export async function getDominantColor(
image: HTMLImageElement | undefined | null,
options: DominantColorOptions = {},
): Promise<string> {
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)
}
// 预加载图片

View File

@@ -118,6 +118,7 @@ async function fetchData({ done }: { done: any }) {
page.value++
// 返回加载成功
done('ok')
await nextTick()
}
} else {
// 加载一次

View File

@@ -86,6 +86,7 @@ async function fetchData({ done }: { done: any }) {
page.value++
// 返回加载成功
done('ok')
await nextTick()
}
}
} else {

View File

@@ -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')
}

View File

@@ -170,6 +170,7 @@ async function fetchData({ done }: { done: any }) {
page.value++
// 返回加载成功
done('ok')
await nextTick()
}
} else {
// 设置加载中

View File

@@ -184,6 +184,7 @@ async function fetchData({ done }: { done: any }) {
page.value++
// 返回加载成功
done('ok')
await nextTick()
}
} else {
// 设置加载中

View File

@@ -110,6 +110,7 @@ async function fetchData({ done }: { done: any }) {
page.value++
// 返回加载成功
done('ok')
await nextTick()
}
} else {
// 设置加载中