Skip to content

Commit d9e3ebd

Browse files
committed
feat: 优化代码
1 parent 2a87f38 commit d9e3ebd

File tree

2 files changed

+244
-260
lines changed

2 files changed

+244
-260
lines changed

components/modals/IconifySelector.tsx

Lines changed: 105 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
'use client';
22

3-
import React, { useState, useCallback, useRef, useEffect } from 'react';
3+
import React, { useState, useCallback, useRef, useEffect, useMemo } from 'react';
44
import { Button, Input, List, Popover, Spin, Empty, ColorPicker } from 'antd';
55
import type { Color } from 'antd/es/color-picker';
66
import { SearchOutlined, CheckOutlined } from '@ant-design/icons';
77
import { iconifyApi, type IconOption } from '@/api/iconify';
88
import { PRESET_COLORS } from '@/utils/colorUtils';
9+
import { debounce } from '@/utils/debounce';
910

1011
/**
1112
* IconifySelector 组件 Props
@@ -25,29 +26,6 @@ interface IconifySelectorProps {
2526
iconColor?: string;
2627
}
2728

28-
/**
29-
* 防抖函数
30-
*/
31-
function debounce<T extends (...args: any[]) => any>(
32-
func: T,
33-
delay: number
34-
): (...args: Parameters<T>) => void {
35-
let timeoutId: NodeJS.Timeout | null = null;
36-
37-
return function debounced(...args: Parameters<T>) {
38-
// 清除之前的定时器
39-
if (timeoutId) {
40-
clearTimeout(timeoutId);
41-
}
42-
43-
// 设置新的定时器
44-
timeoutId = setTimeout(() => {
45-
func(...args);
46-
timeoutId = null;
47-
}, delay);
48-
};
49-
}
50-
5129
/**
5230
* 图标选项渲染组件
5331
*/
@@ -93,13 +71,9 @@ const IconOptionItem: React.FC<{
9371
?
9472
</div>
9573
)}
96-
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
97-
{icon.label}
98-
</span>
74+
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{icon.label}</span>
9975
</div>
100-
{selected && (
101-
<CheckOutlined className="text-blue-500 shrink-0 ml-2" />
102-
)}
76+
{selected && <CheckOutlined className="text-blue-500 shrink-0 ml-2" />}
10377
</div>
10478
</List.Item>
10579
);
@@ -123,17 +97,19 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
12397
const [options, setOptions] = useState<IconOption[]>([]);
12498
const [selectedIcon, setSelectedIcon] = useState<IconOption | null>(null);
12599
const [error, setError] = useState<string>('');
126-
100+
127101
const searchInputRef = useRef<any>(null);
128-
const searchCacheRef = useRef<Map<string, { results: IconOption[]; timestamp: number }>>(new Map());
102+
const searchCacheRef = useRef<Map<string, { results: IconOption[]; timestamp: number }>>(
103+
new Map()
104+
);
129105
const CACHE_DURATION = 5 * 60 * 1000; // 5 分钟
130106

131107
// 初始化:如果有 value,尝试解析为 IconOption
132108
useEffect(() => {
133109
if (value) {
134110
const identifier = extractIconIdentifier(value);
135111
console.log('IconifySelector 初始化:', { value, identifier });
136-
112+
137113
if (identifier && iconifyApi.isValidIconIdentifier(identifier)) {
138114
const label = identifier.split(':')[1] || identifier;
139115
console.log('设置选中图标:', { identifier, label });
@@ -156,60 +132,60 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
156132
};
157133

158134
// 搜索图标
159-
const searchIcons = useCallback(async (searchQuery: string) => {
160-
if (!searchQuery || searchQuery.trim() === '') {
161-
setOptions([]);
162-
setError('');
163-
return;
164-
}
135+
const searchIcons = useCallback(
136+
async (searchQuery: string) => {
137+
if (!searchQuery || searchQuery.trim() === '') {
138+
setOptions([]);
139+
setError('');
140+
return;
141+
}
165142

166-
const trimmedQuery = searchQuery.trim().toLowerCase();
143+
const trimmedQuery = searchQuery.trim().toLowerCase();
167144

168-
// 检查缓存
169-
const cached = searchCacheRef.current.get(trimmedQuery);
170-
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
171-
console.log('使用缓存的搜索结果:', trimmedQuery);
172-
setOptions(cached.results);
173-
if (cached.results.length === 0) {
174-
setError('未找到相关图标');
145+
// 检查缓存
146+
const cached = searchCacheRef.current.get(trimmedQuery);
147+
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
148+
console.log('使用缓存的搜索结果:', trimmedQuery);
149+
setOptions(cached.results);
150+
if (cached.results.length === 0) {
151+
setError('未找到相关图标');
152+
}
153+
return;
175154
}
176-
return;
177-
}
178155

179-
setLoading(true);
180-
setError('');
156+
setLoading(true);
157+
setError('');
181158

182-
try {
183-
const results = await iconifyApi.searchIcons({
184-
query: trimmedQuery,
185-
limit: 200,
186-
});
159+
try {
160+
const results = await iconifyApi.searchIcons({
161+
query: trimmedQuery,
162+
limit: 200,
163+
});
164+
165+
// 缓存结果
166+
searchCacheRef.current.set(trimmedQuery, {
167+
results,
168+
timestamp: Date.now(),
169+
});
187170

188-
// 缓存结果
189-
searchCacheRef.current.set(trimmedQuery, {
190-
results,
191-
timestamp: Date.now(),
192-
});
171+
setOptions(results);
193172

194-
setOptions(results);
195-
196-
if (results.length === 0) {
197-
setError('未找到相关图标');
173+
if (results.length === 0) {
174+
setError('未找到相关图标');
175+
}
176+
} catch (err) {
177+
console.error('搜索图标失败:', err);
178+
setError('搜索失败,请稍后重试');
179+
setOptions([]);
180+
} finally {
181+
setLoading(false);
198182
}
199-
} catch (err) {
200-
console.error('搜索图标失败:', err);
201-
setError('搜索失败,请稍后重试');
202-
setOptions([]);
203-
} finally {
204-
setLoading(false);
205-
}
206-
}, [CACHE_DURATION]);
183+
},
184+
[CACHE_DURATION]
185+
);
207186

208187
// 防抖搜索
209-
const debouncedSearch = useCallback(
210-
debounce(searchIcons, 500),
211-
[searchIcons]
212-
);
188+
const debouncedSearch = useMemo(() => debounce(searchIcons, 500), [searchIcons]);
213189

214190
// 处理搜索输入
215191
const handleSearchChange = useCallback(
@@ -226,7 +202,7 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
226202
(icon: IconOption) => {
227203
setSelectedIcon(icon);
228204
setOpen(false);
229-
205+
230206
if (onChange) {
231207
// 如果有颜色,添加 color 参数
232208
const iconUrl = iconColor ? `${icon.url}?color=${encodeURIComponent(iconColor)}` : icon.url;
@@ -237,44 +213,44 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
237213
);
238214

239215
// 处理键盘事件
240-
const handleKeyDown = useCallback(
241-
(e: React.KeyboardEvent) => {
242-
if (e.key === 'Escape') {
243-
setOpen(false);
244-
}
245-
},
246-
[]
247-
);
216+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
217+
if (e.key === 'Escape') {
218+
setOpen(false);
219+
}
220+
}, []);
248221

249222
// 处理下拉框打开
250-
const handleOpenChange = useCallback((visible: boolean) => {
251-
console.log('下拉框状态变化:', { visible, selectedIcon });
252-
setOpen(visible);
253-
254-
if (visible) {
255-
// 打开时,如果有选中的图标,回填图标名称到搜索框
256-
if (selectedIcon) {
257-
console.log('回填图标名称:', selectedIcon.label);
258-
setQuery(selectedIcon.label);
259-
// 自动搜索该图标名称
260-
searchIcons(selectedIcon.label);
223+
const handleOpenChange = useCallback(
224+
(visible: boolean) => {
225+
console.log('下拉框状态变化:', { visible, selectedIcon });
226+
setOpen(visible);
227+
228+
if (visible) {
229+
// 打开时,如果有选中的图标,回填图标名称到搜索框
230+
if (selectedIcon) {
231+
console.log('回填图标名称:', selectedIcon.label);
232+
setQuery(selectedIcon.label);
233+
// 自动搜索该图标名称
234+
searchIcons(selectedIcon.label);
235+
}
236+
// 聚焦搜索框
237+
setTimeout(() => {
238+
searchInputRef.current?.focus();
239+
}, 100);
240+
} else {
241+
// 关闭时清空搜索
242+
setQuery('');
243+
setOptions([]);
244+
setError('');
261245
}
262-
// 聚焦搜索框
263-
setTimeout(() => {
264-
searchInputRef.current?.focus();
265-
}, 100);
266-
} else {
267-
// 关闭时清空搜索
268-
setQuery('');
269-
setOptions([]);
270-
setError('');
271-
}
272-
}, [selectedIcon, searchIcons]);
246+
},
247+
[selectedIcon, searchIcons]
248+
);
273249

274250
// 渲染下拉内容
275251
const renderContent = () => (
276-
<div
277-
className="w-80"
252+
<div
253+
className="w-80"
278254
style={{ maxHeight: '400px' }}
279255
onKeyDown={handleKeyDown}
280256
role="dialog"
@@ -294,8 +270,8 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
294270
</div>
295271

296272
{/* 图标列表 */}
297-
<div
298-
className="overflow-y-auto"
273+
<div
274+
className="overflow-y-auto"
299275
style={{ maxHeight: '320px' }}
300276
role="listbox"
301277
aria-label="图标列表"
@@ -307,11 +283,7 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
307283
</div>
308284
) : error ? (
309285
<div role="alert">
310-
<Empty
311-
image={Empty.PRESENTED_IMAGE_SIMPLE}
312-
description={error}
313-
className="py-8"
314-
/>
286+
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={error} className="py-8" />
315287
</div>
316288
) : options.length > 0 ? (
317289
<List
@@ -337,12 +309,15 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
337309
);
338310

339311
// 处理颜色变化
340-
const handleColorChange = useCallback((color: Color) => {
341-
const colorValue = color.toHexString();
342-
if (onColorChange) {
343-
onColorChange(colorValue);
344-
}
345-
}, [onColorChange]);
312+
const handleColorChange = useCallback(
313+
(color: Color) => {
314+
const colorValue = color.toHexString();
315+
if (onColorChange) {
316+
onColorChange(colorValue);
317+
}
318+
},
319+
[onColorChange]
320+
);
346321

347322
return (
348323
<div className="flex items-center flex-1">
@@ -364,24 +339,26 @@ export const IconifySelector: React.FC<IconifySelectorProps> = ({
364339
{selectedIcon ? (
365340
<div className="flex items-center gap-2">
366341
<img
367-
src={iconColor ? `${selectedIcon.url}?color=${encodeURIComponent(iconColor)}` : selectedIcon.url}
342+
src={
343+
iconColor
344+
? `${selectedIcon.url}?color=${encodeURIComponent(iconColor)}`
345+
: selectedIcon.url
346+
}
368347
alt={selectedIcon.label}
369348
className="w-5 h-5"
370349
onError={(e) => {
371350
e.currentTarget.style.display = 'none';
372351
}}
373352
/>
374-
<span className="overflow-hidden text-ellipsis">
375-
{selectedIcon.label}
376-
</span>
353+
<span className="overflow-hidden text-ellipsis">{selectedIcon.label}</span>
377354
</div>
378355
) : (
379356
<span className="text-gray-400">选择 Iconify 图标</span>
380357
)}
381358
</Button>
382359
</Popover>
383360
<ColorPicker
384-
className='w-28'
361+
className="w-28"
385362
value={iconColor || '#000000'}
386363
onChange={handleColorChange}
387364
presets={[

0 commit comments

Comments
 (0)