feat(搜索): 添加防抖功能优化搜索性能

This commit is contained in:
zhang1106
2026-02-10 14:17:26 +08:00
parent afc372a432
commit 18c3a3eb15
6 changed files with 91 additions and 11 deletions
+74
View File
@@ -0,0 +1,74 @@
import { useState, useEffect } from 'react';
/**
* 防抖 Hook
* @param {any} value - 需要防抖的值
* @param {number} delay - 延迟时间(毫秒)
* @returns {any} 防抖后的值
*
* @example
* const [keyword, setKeyword] = useState('');
* const debouncedKeyword = useDebounce(keyword, 300);
*
* useEffect(() => {
* // 只有在停止输入 300ms 后才会执行
* fetchData(debouncedKeyword);
* }, [debouncedKeyword]);
*/
export function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(timer);
};
}, [value, delay]);
return debouncedValue;
}
/**
* 防抖函数 Hook(用于回调函数)
* @param {Function} callback - 需要防抖的回调函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function} 防抖后的函数
*
* @example
* const debouncedSearch = useDebouncedCallback((value) => {
* fetchData(value);
* }, 300);
*
* <Input onChange={e => debouncedSearch(e.target.value)} />
*/
export function useDebouncedCallback(callback, delay = 300) {
const [timeoutId, setTimeoutId] = useState(null);
const debouncedFn = (...args) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
const newTimeoutId = setTimeout(() => {
callback(...args);
}, delay);
setTimeoutId(newTimeoutId);
};
// 清理函数
useEffect(() => {
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, [timeoutId]);
return debouncedFn;
}
export default useDebounce;
+4 -2
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { import {
Table, Table,
Button, Button,
@@ -31,13 +32,14 @@ function CategoryManagement() {
showTotal: total => `${total} 条记录`, showTotal: total => `${total} 条记录`,
}); });
const [keyword, setKeyword] = useState(''); const [keyword, setKeyword] = useState('');
const debouncedKeyword = useDebounce(keyword, 300);
const [status, setStatus] = useState('all'); const [status, setStatus] = useState('all');
const fetchCategories = async (page = 1, pageSize = 10) => { const fetchCategories = async (page = 1, pageSize = 10) => {
try { try {
setLoading(true); setLoading(true);
const response = await axios.get('/api/consumable-categories', { const response = await axios.get('/api/consumable-categories', {
params: { page, pageSize, keyword, status }, params: { page, pageSize, keyword: debouncedKeyword, status },
}); });
setCategories(response.data.categories); setCategories(response.data.categories);
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total })); setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
@@ -51,7 +53,7 @@ function CategoryManagement() {
useEffect(() => { useEffect(() => {
fetchCategories(); fetchCategories();
}, [keyword, status]); }, [debouncedKeyword, status]);
const showModal = (category = null) => { const showModal = (category = null) => {
setEditingCategory(category); setEditingCategory(category);
+4 -2
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { import {
Table, Table,
Button, Button,
@@ -45,6 +46,7 @@ function ConsumableManagement() {
showTotal: total => `${total} 条记录`, showTotal: total => `${total} 条记录`,
}); });
const [keyword, setKeyword] = useState(''); const [keyword, setKeyword] = useState('');
const debouncedKeyword = useDebounce(keyword, 300);
const [category, setCategory] = useState('all'); const [category, setCategory] = useState('all');
const [status, setStatus] = useState('all'); const [status, setStatus] = useState('all');
const [importModalVisible, setImportModalVisible] = useState(false); const [importModalVisible, setImportModalVisible] = useState(false);
@@ -65,7 +67,7 @@ function ConsumableManagement() {
try { try {
setLoading(true); setLoading(true);
const response = await axios.get('/api/consumables', { const response = await axios.get('/api/consumables', {
params: { page, pageSize, keyword, category, status }, params: { page, pageSize, keyword: debouncedKeyword, category, status },
}); });
setConsumables(response.data.consumables); setConsumables(response.data.consumables);
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total })); setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
@@ -76,7 +78,7 @@ function ConsumableManagement() {
setLoading(false); setLoading(false);
} }
}, },
[keyword, category, status] [debouncedKeyword, category, status]
); );
const fetchCategories = useCallback(async () => { const fetchCategories = useCallback(async () => {
+3 -3
View File
@@ -1433,7 +1433,7 @@ function DeviceManagement() {
value={status} value={status}
onChange={setStatus} onChange={setStatus}
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }} style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }} styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }}
> >
<Option value="all">所有状态</Option> <Option value="all">所有状态</Option>
<Option value="running">运行中</Option> <Option value="running">运行中</Option>
@@ -1448,7 +1448,7 @@ function DeviceManagement() {
value={type} value={type}
onChange={setType} onChange={setType}
style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }} style={{ width: '140px', borderRadius: designTokens.borderRadius.medium }}
dropdownStyle={{ borderRadius: designTokens.borderRadius.medium }} styles={{ popup: { root: { borderRadius: designTokens.borderRadius.medium } } }}
> >
<Option value="all">所有类型</Option> <Option value="all">所有类型</Option>
<Option value="server">服务器</Option> <Option value="server">服务器</Option>
@@ -1547,7 +1547,7 @@ function DeviceManagement() {
style: { marginTop: '16px' }, style: { marginTop: '16px' },
}} }}
onChange={handleTableChange} onChange={handleTableChange}
scroll={{ y: 'calc(100vh - 380px)', scrollToFirstRowOnChange: true }} scroll={{ y: 500, scrollToFirstRowOnChange: true }}
virtual virtual
components={{ components={{
header: { header: {
+5 -3
View File
@@ -1,4 +1,5 @@
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useDebounce } from '../hooks/useDebounce';
import { import {
Table, Table,
Button, Button,
@@ -340,6 +341,7 @@ function RackManagement() {
const [selectedRackIds, setSelectedRackIds] = useState([]); const [selectedRackIds, setSelectedRackIds] = useState([]);
const [viewMode, setViewMode] = useState('table'); const [viewMode, setViewMode] = useState('table');
const [searchKeyword, setSearchKeyword] = useState(''); const [searchKeyword, setSearchKeyword] = useState('');
const debouncedSearchKeyword = useDebounce(searchKeyword, 300);
const [statusFilter, setStatusFilter] = useState('all'); const [statusFilter, setStatusFilter] = useState('all');
const [roomFilter, setRoomFilter] = useState('all'); const [roomFilter, setRoomFilter] = useState('all');
const [pagination, setPagination] = useState({ const [pagination, setPagination] = useState({
@@ -366,7 +368,7 @@ function RackManagement() {
pageSize, pageSize,
roomId: roomFilter, roomId: roomFilter,
status: statusFilter, status: statusFilter,
keyword: searchKeyword || undefined, keyword: debouncedSearchKeyword || undefined,
}, },
}); });
const { racks: data, total } = response.data; const { racks: data, total } = response.data;
@@ -379,7 +381,7 @@ function RackManagement() {
setLoading(false); setLoading(false);
} }
}, },
[roomFilter, statusFilter, searchKeyword] [roomFilter, statusFilter, debouncedSearchKeyword]
); );
const fetchRooms = useCallback(async () => { const fetchRooms = useCallback(async () => {
@@ -401,7 +403,7 @@ function RackManagement() {
useEffect(() => { useEffect(() => {
setPagination(prev => ({ ...prev, current: 1 })); setPagination(prev => ({ ...prev, current: 1 }));
fetchRacks(1, pagination.pageSize); fetchRacks(1, pagination.pageSize);
}, [roomFilter, statusFilter, searchKeyword]); }, [roomFilter, statusFilter, debouncedSearchKeyword]);
const handleTableChange = useCallback( const handleTableChange = useCallback(
pagination => { pagination => {
+1 -1
View File
@@ -660,7 +660,7 @@ function RoomManagement() {
value={statusFilter} value={statusFilter}
onChange={setStatusFilter} onChange={setStatusFilter}
style={{ width: '140px', height: '42px' }} style={{ width: '140px', height: '42px' }}
dropdownStyle={{ borderRadius: '10px' }} styles={{ popup: { root: { borderRadius: '10px' } } }}
> >
<Option value="all">全部状态</Option> <Option value="all">全部状态</Option>
<Option value="active">在用</Option> <Option value="active">在用</Option>