Skip to content

Commit eff34ac

Browse files
committed
fix: 优化图标仓库弹窗组件滚动性能和内存占用
1 parent 28bfd45 commit eff34ac

2 files changed

Lines changed: 82 additions & 46 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "sub-store-front-end",
3-
"version": "2.28.0",
3+
"version": "2.28.1",
44
"private": true,
55
"packageManager": "pnpm@11.0.9",
66
"scripts": {

src/views/icon/IconPopup.vue

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,32 @@
108108
</nut-button>
109109
</div>
110110
</div>
111-
<div v-else-if="iconData.length" class="icon-list">
112-
<div
113-
v-for="(icon, index) in iconData"
114-
:key="index"
115-
class="icon-item"
116-
@click="handleIcon(icon)"
117-
>
118-
<nut-image
119-
:src="rewriteGithubUrl(icon.url)"
120-
:fit="globalIconFit"
121-
lazy-load
122-
show-loading
123-
/>
124-
<p>{{ icon.name }}</p>
111+
<div v-else-if="iconData.length" class="icon-list" :ref="(el) => { (containerProps.ref as any).value = el }" @scroll="containerProps.onScroll">
112+
<div v-bind="wrapperProps">
113+
<div
114+
v-for="row in virtualRows"
115+
:key="row.index"
116+
class="icon-row"
117+
>
118+
<div
119+
v-for="(icon, iconIndex) in row.data"
120+
:key="iconIndex"
121+
class="icon-item"
122+
:style="{
123+
width: iconItemWidth,
124+
marginRight: iconIndex < row.data.length - 1 ? iconItemGap : '0',
125+
}"
126+
@click="handleIcon(icon)"
127+
>
128+
<nut-image
129+
:src="rewriteGithubUrl(icon.url)"
130+
:fit="globalIconFit"
131+
lazy-load
132+
show-loading
133+
/>
134+
<p>{{ icon.name }}</p>
135+
</div>
136+
</div>
125137
</div>
126138
</div>
127139
<div v-else class="icon-state-wrapper">
@@ -150,7 +162,7 @@
150162
<script setup lang="ts">
151163
import DesktopPicker from "@/components/DesktopPicker.vue";
152164
import { Toast } from "@nutui/nutui";
153-
import { useDebounceFn } from "@vueuse/core";
165+
import { useDebounceFn, useVirtualList } from "@vueuse/core";
154166
import axios from "axios";
155167
import { storeToRefs } from "pinia";
156168
import { computed, reactive, ref, watch } from "vue";
@@ -161,6 +173,7 @@ import { useSettingsStore } from "@/store/settings";
161173
import { createGithubProxyUrlRewriter } from "@/utils/githubProxy";
162174
import { normalizeImageFit } from "@/utils/iconFit";
163175
import { getApiRequestTimeout } from "@/utils/requestTimeout";
176+
import { useWideScreenNarrowMode } from "@/hooks/useWideScreenNarrowMode";
164177
165178
const props = defineProps({
166179
visible: {
@@ -202,6 +215,8 @@ const performSearch = () => {
202215
item.name.toLowerCase().includes(lowercaseTerm),
203216
);
204217
}
218+
// 搜索后滚动到顶部
219+
scrollToRow(0);
205220
};
206221
207222
// 使用 useDebounceFn 创建防抖搜索函数
@@ -210,6 +225,32 @@ const debouncedSearch = useDebounceFn(performSearch, 500);
210225
const iconData = computed(() => {
211226
return searchResult.value;
212227
});
228+
// 宽屏(>=768px)每行显示 8 个,窄屏每行显示 4 个
229+
const { isWideScreen } = useWideScreenNarrowMode();
230+
const iconsPerRow = computed(() => (isWideScreen.value ? 8 : 4));
231+
const iconItemWidth = computed(() => `${(96 / iconsPerRow.value).toFixed(4)}%`);
232+
const iconItemGap = computed(() => `calc(4% / ${iconsPerRow.value - 1})`);
233+
// 将图标数据按每行数量分组
234+
const iconRows = computed(() => {
235+
const rows: { name: string; url: string }[][] = [];
236+
const icons = iconData.value;
237+
const perRow = iconsPerRow.value;
238+
for (let i = 0; i < icons.length; i += perRow) {
239+
rows.push(icons.slice(i, i + perRow));
240+
}
241+
return rows;
242+
});
243+
// 使用 useVirtualList 进行虚拟滚动
244+
const {
245+
list: virtualRows,
246+
containerProps,
247+
wrapperProps,
248+
scrollTo: scrollToRow,
249+
} = useVirtualList(iconRows, {
250+
itemHeight: 90,
251+
overscan: 5,
252+
});
253+
213254
const globalIconFit = computed(() => normalizeImageFit(appearanceSetting.value.iconFit));
214255
const githubUrlRewriter = computed(() => {
215256
return createGithubProxyUrlRewriter(githubProxy.value, githubProxyRegex.value);
@@ -560,43 +601,38 @@ defineExpose({ show, hide, close });
560601
margin-top: 8px;
561602
}
562603
.icon-list {
563-
display: flex;
564-
flex-wrap: wrap;
565-
align-content: flex-start;
566604
height: 100%;
567605
min-height: 0;
568606
overflow-y: auto;
569607
-webkit-overflow-scrolling: touch;
570-
padding-bottom: 20px;
571-
.icon-item {
572-
text-align: center;
573-
width: 24%;
608+
.icon-row {
574609
display: flex;
575-
flex-direction: column;
576-
align-items: center;
577-
margin-bottom: 20px;
578-
&:not(:nth-child(4n)) {
579-
margin-right: calc(4% / 3);
580-
}
581-
.nut-image {
582-
cursor: pointer;
583-
width: 50px;
584-
height: 50px;
585-
border-radius: 10px;
586-
overflow: hidden;
587-
}
588-
.sub-item-customer-icon {
589-
:deep(img) {
590-
& {
591-
opacity: 0.8;
592-
filter: brightness(var(--img-brightness));
610+
padding-bottom: 20px;
611+
.icon-item {
612+
text-align: center;
613+
display: flex;
614+
flex-direction: column;
615+
align-items: center;
616+
.nut-image {
617+
cursor: pointer;
618+
width: 50px;
619+
height: 50px;
620+
border-radius: 10px;
621+
overflow: hidden;
622+
}
623+
.sub-item-customer-icon {
624+
:deep(img) {
625+
& {
626+
opacity: 0.8;
627+
filter: brightness(var(--img-brightness));
628+
}
593629
}
594630
}
595-
}
596-
p {
597-
font-size: 12px;
598-
color: var(--primary-text-color);
599-
word-break: break-all;
631+
p {
632+
font-size: 12px;
633+
color: var(--primary-text-color);
634+
word-break: break-all;
635+
}
600636
}
601637
}
602638
}

0 commit comments

Comments
 (0)