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" >
150162<script setup lang="ts">
151163import DesktopPicker from " @/components/DesktopPicker.vue" ;
152164import { Toast } from " @nutui/nutui" ;
153- import { useDebounceFn } from " @vueuse/core" ;
165+ import { useDebounceFn , useVirtualList } from " @vueuse/core" ;
154166import axios from " axios" ;
155167import { storeToRefs } from " pinia" ;
156168import { computed , reactive , ref , watch } from " vue" ;
@@ -161,6 +173,7 @@ import { useSettingsStore } from "@/store/settings";
161173import { createGithubProxyUrlRewriter } from " @/utils/githubProxy" ;
162174import { normalizeImageFit } from " @/utils/iconFit" ;
163175import { getApiRequestTimeout } from " @/utils/requestTimeout" ;
176+ import { useWideScreenNarrowMode } from " @/hooks/useWideScreenNarrowMode" ;
164177
165178const 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);
210225const 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+
213254const globalIconFit = computed (() => normalizeImageFit (appearanceSetting .value .iconFit ));
214255const 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 : 12 px ;
598- color : var ( --primary-text-color ) ;
599- word-break : break-all ;
631+ p {
632+ font-size : 12 px ;
633+ color : var ( --primary-text-color ) ;
634+ word-break : break-all ;
635+ }
600636 }
601637 }
602638 }
0 commit comments