diff --git a/electron/main/ipc/ipc-file.ts b/electron/main/ipc/ipc-file.ts index 14bc4a9..e0489ba 100644 --- a/electron/main/ipc/ipc-file.ts +++ b/electron/main/ipc/ipc-file.ts @@ -27,7 +27,7 @@ const initFileIpc = (): void => { const filePath = resolve(dirPath).replace(/\\/g, "/"); console.info(`📂 Fetching music files from: ${filePath}`); // 查找指定目录下的所有音乐文件 - const musicFiles = await FastGlob("**/*.{mp3,wav,flac}", { cwd: filePath }); + const musicFiles = await FastGlob("**/*.{mp3,wav,flac,aac,webm}", { cwd: filePath }); // 解析元信息 const metadataPromises = musicFiles.map(async (file) => { const filePath = join(dirPath, file); diff --git a/src/assets/icons/Close.svg b/src/assets/icons/Close.svg new file mode 100644 index 0000000..798a94f --- /dev/null +++ b/src/assets/icons/Close.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/Lock.svg b/src/assets/icons/Lock.svg new file mode 100644 index 0000000..cc784c5 --- /dev/null +++ b/src/assets/icons/Lock.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/LockOpen.svg b/src/assets/icons/LockOpen.svg new file mode 100644 index 0000000..4787547 --- /dev/null +++ b/src/assets/icons/LockOpen.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/icons/TextSizeAdd.svg b/src/assets/icons/TextSizeAdd.svg new file mode 100644 index 0000000..fd1c35b --- /dev/null +++ b/src/assets/icons/TextSizeAdd.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/TextSizeReduce.svg b/src/assets/icons/TextSizeReduce.svg new file mode 100644 index 0000000..bac0878 --- /dev/null +++ b/src/assets/icons/TextSizeReduce.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/components/Global/SvgIcon.vue b/src/components/Global/SvgIcon.vue index 352a335..e36456a 100644 --- a/src/components/Global/SvgIcon.vue +++ b/src/components/Global/SvgIcon.vue @@ -1,7 +1,11 @@ @@ -10,6 +14,7 @@ const props = defineProps<{ name: string; size?: string | number; color?: string; + offset?: number; depth?: 1 | 2 | 3 | 4 | 5; }>(); diff --git a/src/components/Player/PlayerBackground.vue b/src/components/Player/PlayerBackground.vue index 746fc60..391e323 100644 --- a/src/components/Player/PlayerBackground.vue +++ b/src/components/Player/PlayerBackground.vue @@ -14,6 +14,7 @@ diff --git a/src/components/Player/PlayerCover.vue b/src/components/Player/PlayerCover.vue index 6b3def7..8682d05 100644 --- a/src/components/Player/PlayerCover.vue +++ b/src/components/Player/PlayerCover.vue @@ -11,6 +11,7 @@ diff --git a/src/components/UI/s-image.vue b/src/components/UI/s-image.vue index beb8857..12ec4e9 100644 --- a/src/components/UI/s-image.vue +++ b/src/components/UI/s-image.vue @@ -14,6 +14,8 @@ :key="imgSrc" :alt="alt || 'image'" :class="['cover', { loaded: isLoaded }]" + :decoding="decodeAsync ? 'async' : 'auto'" + :loading="nativeLazy ? 'lazy' : 'eager'" @load="imageLoaded" @error="imageError" /> @@ -27,9 +29,21 @@ const props = withDefaults( src: string | undefined; defaultSrc?: string; alt?: string; + // 是否进行可视状态变化 + observeVisibility?: boolean; + // 在不可视时是否释放图片以回收内存 + releaseOnHide?: boolean; + // 是否使用浏览器异步解码 + decodeAsync?: boolean; + // 是否使用原生懒加载 + nativeLazy?: boolean; }>(), { defaultSrc: "/images/song.jpg?assest", + observeVisibility: true, + releaseOnHide: false, + decodeAsync: true, + nativeLazy: true, }, ); @@ -49,31 +63,57 @@ const imgContainer = ref(); // 是否加载完成 const isLoaded = ref(false); +// 可视状态上一次值,避免重复 emit +const lastShowState = ref(null); +// 加载竞态 token,防止旧图片回调覆盖新状态 +const loadToken = ref(0); +const currentToken = ref(0); // 是否可视 const isCanLook = useElementVisibility(imgContainer); // 图片加载完成 const imageLoaded = (e: Event) => { + // 竞态保护:仅响应最新一次设置的图片 + if (currentToken.value !== loadToken.value) return; + if (isLoaded.value) return; isLoaded.value = true; - // 加载完成 emit("load", e); }; // 图片加载失败 const imageError = (e: Event) => { + // 竞态保护 + if (currentToken.value !== loadToken.value) return; isLoaded.value = false; - imgSrc.value = props.defaultSrc; - // 加载失败 + // 避免默认图也反复触发导致死循环 + if (imgSrc.value !== props.defaultSrc) { + imgSrc.value = props.defaultSrc; + } emit("error", e); }; -// 可视状态变化 +// 可视状态变化(可控) watch( isCanLook, (show) => { - emit("update:show", show); - if (show) imgSrc.value = props.src; + if (!props.observeVisibility) return; + // 去重:仅在状态变化时触发 + if (lastShowState.value !== show) { + lastShowState.value = show; + emit("update:show", show); + } + if (show) { + // 进入可视区再加载,避免重复赋值 + if (imgSrc.value !== props.src) { + loadToken.value += 1; + currentToken.value = loadToken.value; + imgSrc.value = props.src; + } + } else if (props.releaseOnHide) { + // 释放图片以回收内存 + if (imgSrc.value !== undefined) imgSrc.value = undefined; + } }, { immediate: true }, ); @@ -83,13 +123,40 @@ watch( () => props.src, (val) => { isLoaded.value = false; - if (isCanLook.value) { - imgSrc.value = val; + // 不同值时才进行赋值,减少重绘 + if (props.observeVisibility) { + if (isCanLook.value) { + if (imgSrc.value !== val) { + loadToken.value += 1; + currentToken.value = loadToken.value; + imgSrc.value = val; + } + } else { + if (props.releaseOnHide) { + if (imgSrc.value !== undefined) imgSrc.value = undefined; + } + } } else { - imgSrc.value = undefined; + if (imgSrc.value !== val) { + loadToken.value += 1; + currentToken.value = loadToken.value; + imgSrc.value = val; + } } }, + { immediate: true }, ); + +onUnmounted(() => { + try { + if (imgRef.value) imgRef.value.src = ""; + } catch { + /* empty */ + } + imgSrc.value = undefined; + imgRef.value = undefined; + imgContainer.value = undefined; +}); - + diff --git a/tsconfig.web.json b/tsconfig.web.json index 2424170..aea32a7 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -17,7 +17,7 @@ "paths": { "@/*": ["src/*"] }, - "types": ["node", "electron", "electron-vite/node", "./auto-imports.d.ts"], + "types": ["node", "electron", "electron-vite/node", "./auto-imports.d.ts", "./components.d.ts"], "lib": ["ES2022", "DOM", "DOM.Iterable"], "target": "es2022" }