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"
}