diff --git a/.gitignore b/.gitignore index 0ff483a1..879aa1eb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,3 @@ package-lock.json # iconify dist files src/@iconify/*.js public/plugin_icon/** - -# AI -.omc/ \ No newline at end of file diff --git a/package.json b/package.json index 42070e4f..e128f639 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,6 @@ { "name": "moviepilot", "version": "2.11.4", - "license": "MIT", "private": true, "type": "module", "bin": "dist/service.js", @@ -29,7 +28,6 @@ "@fullcalendar/timegrid": "^6.1.15", "@fullcalendar/vue3": "^6.1.15", "@iconify/utils": "^2.2.1", - "@tanstack/vue-virtual": "^3.13.24", "@types/crypto-js": "^4.2.2", "@types/js-cookie": "^3.0.6", "@vue-flow/background": "^1.3.2", diff --git a/src/@core/utils/image.ts b/src/@core/utils/image.ts index 9c0e1ace..546c0d92 100644 --- a/src/@core/utils/image.ts +++ b/src/@core/utils/image.ts @@ -14,95 +14,33 @@ function rgbStringToHex(rgbArray: number[]): string { return `#${toHex(r)}${toHex(g)}${toHex(b)}` } -// 主色调缓存:相同 URL 不重复经过 ColorThief 的 canvas 解码 -const DOMINANT_COLOR_CACHE_MAX = 200 -const dominantColorCache = new Map() - -function rememberDominantColor(key: string, value: string) { - if (!key) return - if (dominantColorCache.size >= DOMINANT_COLOR_CACHE_MAX) { - const first = dominantColorCache.keys().next().value - if (first !== undefined) dominantColorCache.delete(first) - } - dominantColorCache.set(key, value) -} - // 提取主要颜色 export async function getDominantColor(image: HTMLImageElement): Promise { - const cacheKey = image?.currentSrc || image?.src || '' - const cached = cacheKey ? dominantColorCache.get(cacheKey) : undefined - if (cached) return cached - try { - const colorThief = new ColorThief() - const dominantColor = colorThief.getColor(image) - const hex = rgbStringToHex(dominantColor) - rememberDominantColor(cacheKey, hex) - return hex - } catch (e) { - console.warn('getDominantColor failed', e) - return '#28A9E1' - } -} - -// 预加载缓存:已成功加载的 URL 不再重复创建 Image 对象 -const PRELOAD_CACHE_MAX = 50 -const preloadedUrls = new Set() - -function rememberPreloaded(url: string) { - if (!url) return - if (preloadedUrls.size >= PRELOAD_CACHE_MAX) { - const first = preloadedUrls.values().next().value - if (first !== undefined) preloadedUrls.delete(first) - } - preloadedUrls.add(url) + const colorThief = new ColorThief() + const dominantColor = colorThief.getColor(image) + return rgbStringToHex(dominantColor) } // 预加载图片 export async function preloadImage(url: string): Promise { - if (!url) return false - if (preloadedUrls.has(url)) return true return new Promise(resolve => { const img = new Image() - img.decoding = 'async' - let settled = false - const finish = (ok: boolean) => { - if (settled) return - settled = true - img.onload = null - img.onerror = null - window.clearTimeout(timeout) - if (ok) rememberPreloaded(url) - else img.src = '' // 释放解码位图 - resolve(ok) - } + img.onload = () => resolve(true) + img.onerror = () => resolve(false) - img.onload = () => finish(true) - img.onerror = () => finish(false) - - const timeout = window.setTimeout(() => finish(false), 5000) + // 设置超时,防止图片长时间加载 + const timeout = setTimeout(() => { + img.src = '' + resolve(false) + }, 5000) // 5秒超时 img.src = url - // 命中浏览器缓存时 onload 可能不会触发 - if (img.complete && img.naturalWidth > 0) finish(true) + // 如果图片已经缓存,onload可能不会触发 + if (img.complete) { + clearTimeout(timeout) + resolve(true) + } }) } - -// TMDB 图片域名地址(仅作为兜底,调用方应优先用 globalSettings.TMDB_IMAGE_DOMAIN) -const TMDB_PATH_RE = /\/t\/p\/(original|w\d+|h\d+|w\d+_and_h\d+_bestv2)\// - -/** - * 把 TMDB 图片 URL 重置到指定渲染尺寸。非 TMDB URL(豆瓣 / Bangumi / 自定义代理)原样返回。 - * 用于在卡片场景下避免下载 / 解码 1MP+ 的原图。 - * - * 常见尺寸:w92 / w154 / w185 / w342 / w500 / w780 / original(poster, backdrop) - * w45 / w185 / h632 / original(profile) - */ -export function tmdbResize( - url: string | undefined | null, - size: 'w92' | 'w154' | 'w185' | 'w342' | 'w500' | 'w780' | 'original', -): string { - if (!url) return '' - return url.replace(TMDB_PATH_RE, `/t/p/${size}/`) -} diff --git a/src/api/index.ts b/src/api/index.ts index 12a18e77..e252dea4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -45,13 +45,6 @@ api.interceptors.response.use( return response.data }, error => { - // 请求被主动取消(路由切换 / 组件卸载触发 requestOptimizer abort)。 - // 这不是错误:原样透传 cancel error,让调用方用 axios.isCancel() 识别并静默处理。 - // 不能落到下面 new Error(error.message) 分支——那会把 cancel 签名抹掉, - // 调用方只能看到一个 message='canceled' 的普通 Error,被迫当错误打日志。 - if (axios.isCancel(error)) { - return Promise.reject(error) - } if (!error.response) { // 网络错误或请求超时 - 通知离线状态管理系统 const isNetworkError = diff --git a/src/components/cards/MediaCard.vue b/src/components/cards/MediaCard.vue index 1568951a..fcf59935 100644 --- a/src/components/cards/MediaCard.vue +++ b/src/components/cards/MediaCard.vue @@ -1,7 +1,6 @@